ai / frontend /src /lib /components /admin /Users /Groups.svelte
fdrah
initial-commit
9e93b10
Raw
History Blame Contribute Delete
7.32 kB
<script>
import { toast } from "svelte-sonner";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
import { onMount, getContext } from "svelte";
import { goto } from "$app/navigation";
import {
REXPRO_NAME,
config,
user,
showSidebar,
knowledge,
} from "$lib/stores";
import { REXPRO_BASE_URL } from "$lib/constants";
import Tooltip from "$lib/components/common/Tooltip.svelte";
import Plus from "$lib/components/icons/Plus.svelte";
import UsersSolid from "$lib/components/icons/UsersSolid.svelte";
import ChevronRight from "$lib/components/icons/ChevronRight.svelte";
import Search from "$lib/components/icons/Search.svelte";
import EditGroupModal from "./Groups/EditGroupModal.svelte";
import GroupItem from "./Groups/GroupItem.svelte";
import XMark from "$lib/components/icons/XMark.svelte";
import ChevronDown from "$lib/components/icons/ChevronDown.svelte";
import Check from "$lib/components/icons/Check.svelte";
import Select from "$lib/components/common/Select.svelte";
import { createNewGroup, getGroups } from "$lib/apis/groups";
import {
getUserDefaultPermissions,
getAllUsers,
updateUserDefaultPermissions,
} from "$lib/apis/users";
const i18n = getContext("i18n");
let loaded = false;
let groups = [];
let query = "";
let sortBy = "members";
const sortItems = [
{ value: "name", label: $i18n.t("Name") },
{ value: "members", label: $i18n.t("Members") },
];
$: filteredGroups = groups
.filter((group) => {
if (query === "") {
return true;
} else {
let name = group.name.toLowerCase();
const q = query.toLowerCase();
return name.includes(q);
}
})
.sort((a, b) => {
if (sortBy === "name") {
return a.name.localeCompare(b.name);
} else if (sortBy === "members") {
return (b.member_count ?? 0) - (a.member_count ?? 0);
}
return 0;
});
let defaultPermissions = {};
let showAddGroupModal = false;
let showDefaultPermissionsModal = false;
const setGroups = async () => {
groups = await getGroups(localStorage.token);
};
const addGroupHandler = async (group) => {
const res = await createNewGroup(localStorage.token, group).catch(
(error) => {
toast.error(`${error}`);
return null;
},
);
if (res) {
toast.success($i18n.t("Group created successfully"));
groups = await getGroups(localStorage.token);
}
};
const updateDefaultPermissionsHandler = async (group) => {
console.debug(group.permissions);
const res = await updateUserDefaultPermissions(
localStorage.token,
group.permissions,
).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
toast.success($i18n.t("Default permissions updated successfully"));
defaultPermissions = await getUserDefaultPermissions(
localStorage.token,
);
}
};
onMount(async () => {
if ($user?.role !== "admin") {
await goto("/");
return;
}
defaultPermissions = await getUserDefaultPermissions(
localStorage.token,
);
await setGroups();
loaded = true;
});
</script>
{#if loaded}
<EditGroupModal
bind:show={showAddGroupModal}
edit={false}
tabs={["general", "permissions"]}
permissions={defaultPermissions}
onSubmit={addGroupHandler}
/>
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
<div class="flex justify-between items-center">
<div
class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0"
>
<div>
{$i18n.t("Groups")}
</div>
<div
class="text-lg font-medium text-gray-500 dark:text-gray-500"
>
{groups.length}
</div>
</div>
<div class="flex w-full justify-end gap-1.5">
<button
class="px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
on:click={() => {
showAddGroupModal = !showAddGroupModal;
}}
>
<Plus className="size-3" strokeWidth="2.5" />
<div class="hidden md:block md:ml-1 text-xs">
{$i18n.t("New Group")}
</div>
</button>
</div>
</div>
</div>
<div
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30"
>
<div class="flex items-center w-full space-x-2 py-0.5 px-3.5">
<div class="flex flex-1">
<div class="self-center ml-1 mr-3">
<Search className="size-3.5" />
</div>
<input
class="w-full text-sm py-1 rounded-r-xl outline-hidden bg-transparent"
bind:value={query}
aria-label={$i18n.t("Search Groups")}
placeholder={$i18n.t("Search Groups")}
/>
{#if query}
<div
class="self-center pl-1.5 translate-y-[0.5px] rounded-l-xl bg-transparent"
>
<button
class="p-0.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
aria-label={$i18n.t("Clear search")}
on:click={() => {
query = "";
}}
>
<XMark className="size-3" strokeWidth="2" />
</button>
</div>
{/if}
</div>
<Select
bind:value={sortBy}
items={sortItems}
placeholder={$i18n.t("Sort by")}
triggerClass="relative flex items-center gap-0.5 px-2.5 py-1.5 text-sm bg-gray-50 dark:bg-gray-850 rounded-xl shrink-0"
align="end"
>
<svelte:fragment slot="trigger" let:selectedLabel>
<span
class="inline-flex h-input px-0.5 outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden"
>
{selectedLabel}
</span>
<ChevronDown className="size-3.5" strokeWidth="2.5" />
</svelte:fragment>
<svelte:fragment slot="item" let:item let:selected>
{item.label}
{#if selected}
<div class="ml-auto">
<Check />
</div>
{/if}
</svelte:fragment>
</Select>
</div>
{#if filteredGroups.length !== 0}
<div class="my-2 px-3 grid grid-cols-1 gap-1">
{#each filteredGroups as group}
<GroupItem {group} {setGroups} {defaultPermissions} />
{/each}
</div>
{:else}
<div
class="w-full h-full flex flex-col justify-center items-center my-16 mb-24"
>
<div class="max-w-md text-center">
<div class="text-3xl mb-3">👥</div>
<div class="text-lg font-medium mb-1">
{$i18n.t("No groups found")}
</div>
<div class="text-gray-500 text-center text-xs">
{$i18n.t(
"Use groups to organize your users and assign permissions.",
)}
</div>
</div>
</div>
{/if}
</div>
<EditGroupModal
bind:show={showDefaultPermissionsModal}
tabs={["permissions"]}
bind:permissions={defaultPermissions}
custom={false}
onSubmit={updateDefaultPermissionsHandler}
/>
<button
class="flex items-center justify-between rounded-lg w-full transition mt-4"
aria-haspopup="dialog"
on:click={() => {
showDefaultPermissionsModal = true;
}}
>
<div class="flex items-center gap-2.5">
<div class="p-1.5 bg-black/5 dark:bg-white/10 rounded-full">
<UsersSolid className="size-4" />
</div>
<div class="text-left">
<div class=" text-sm font-medium">
{$i18n.t("Default permissions")}
</div>
<div class="flex text-xs mt-0.5">
{$i18n.t('applies to all users with the "user" role')}
</div>
</div>
</div>
<div>
<ChevronRight strokeWidth="2.5" />
</div>
</button>
{/if}