mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-12 19:40:12 +00:00
feat: add various improvements to the table component (#961)
Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
This commit is contained in:
@@ -12,9 +12,6 @@
|
||||
import UserGroupForm from './user-group-form.svelte';
|
||||
import UserGroupList from './user-group-list.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let userGroups = $state(data.userGroups);
|
||||
let userGroupsRequestOptions = $state(data.userGroupsRequestOptions);
|
||||
let expandAddUserGroup = $state(false);
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
@@ -79,7 +76,7 @@
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<UserGroupList {userGroups} requestOptions={userGroupsRequestOptions} />
|
||||
<UserGroupList />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const userGroupService = new UserGroupService();
|
||||
|
||||
const userGroupsRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'friendlyName',
|
||||
direction: 'asc'
|
||||
}
|
||||
};
|
||||
|
||||
const userGroups = await userGroupService.list(userGroupsRequestOptions);
|
||||
return { userGroups, userGroupsRequestOptions };
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import CustomClaimService from '$lib/services/custom-claim-service';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
@@ -11,9 +12,9 @@
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucideChevronLeft } from '@lucide/svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { backNavigate } from '../../users/navigate-back-util';
|
||||
import UserGroupForm from '../user-group-form.svelte';
|
||||
import UserSelection from '../user-selection.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let { data } = $props();
|
||||
let userGroup = $state({
|
||||
@@ -23,6 +24,7 @@
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
const customClaimService = new CustomClaimService();
|
||||
const backNavigation = backNavigate('/settings/admin/user-groups');
|
||||
|
||||
async function updateUserGroup(updatedUserGroup: UserGroupCreate) {
|
||||
let success = true;
|
||||
@@ -61,8 +63,8 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<a class="text-muted-foreground flex text-sm" href="/settings/admin/user-groups"
|
||||
><LucideChevronLeft class="size-5" /> {m.back()}</a
|
||||
<button type="button" class="text-muted-foreground flex text-sm" onclick={backNavigation.go}
|
||||
><LucideChevronLeft class="size-5" /> {m.back()}</button
|
||||
>
|
||||
{#if !!userGroup.ldapId}
|
||||
<Badge class="rounded-full" variant="default">{m.ldap()}</Badge>
|
||||
|
||||
@@ -1,29 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import AdvancedTable from '$lib/components/advanced-table.svelte';
|
||||
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
|
||||
import AdvancedTable from '$lib/components/table/advanced-table.svelte';
|
||||
import { Badge } from '$lib/components/ui/badge/index';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type {
|
||||
AdvancedTableColumn,
|
||||
CreateAdvancedTableActions
|
||||
} from '$lib/types/advanced-table.type';
|
||||
import type { UserGroup, UserGroupWithUserCount } from '$lib/types/user-group.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucidePencil, LucideTrash } from '@lucide/svelte';
|
||||
import Ellipsis from '@lucide/svelte/icons/ellipsis';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
userGroups,
|
||||
requestOptions
|
||||
}: {
|
||||
userGroups: Paginated<UserGroupWithUserCount>;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
} = $props();
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
let tableRef: AdvancedTable<UserGroupWithUserCount>;
|
||||
|
||||
export function refresh() {
|
||||
return tableRef?.refresh();
|
||||
}
|
||||
|
||||
const columns: AdvancedTableColumn<UserGroupWithUserCount>[] = [
|
||||
{ label: 'ID', column: 'id', hidden: true },
|
||||
{ label: m.friendly_name(), column: 'friendlyName', sortable: true },
|
||||
{ label: m.name(), column: 'name', sortable: true },
|
||||
{ label: m.user_count(), column: 'userCount', sortable: true },
|
||||
{
|
||||
label: m.created(),
|
||||
column: 'createdAt',
|
||||
sortable: true,
|
||||
hidden: true,
|
||||
value: (item) => new Date(item.createdAt).toLocaleString()
|
||||
},
|
||||
{ label: m.ldap_id(), column: 'ldapId', hidden: true },
|
||||
{ label: m.source(), key: 'source', hidden: !$appConfigStore.ldapEnabled, cell: SourceCell }
|
||||
];
|
||||
|
||||
const actions: CreateAdvancedTableActions<UserGroupWithUserCount> = (group) => [
|
||||
{
|
||||
label: m.edit(),
|
||||
icon: LucidePencil,
|
||||
variant: 'ghost',
|
||||
onClick: (group) => goto(`/settings/admin/user-groups/${group.id}`)
|
||||
},
|
||||
{
|
||||
label: m.delete(),
|
||||
icon: LucideTrash,
|
||||
variant: 'danger',
|
||||
onClick: (group) => deleteUserGroup(group),
|
||||
visible: group.ldapId || $appConfigStore.ldapEnabled
|
||||
}
|
||||
];
|
||||
|
||||
async function deleteUserGroup(userGroup: UserGroup) {
|
||||
openConfirmDialog({
|
||||
@@ -35,7 +64,7 @@
|
||||
action: async () => {
|
||||
try {
|
||||
await userGroupService.remove(userGroup.id);
|
||||
userGroups = await userGroupService.list(requestOptions!);
|
||||
await refresh();
|
||||
toast.success(m.user_group_deleted_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -46,48 +75,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet SourceCell({ item }: { item: UserGroupWithUserCount })}
|
||||
<Badge class="rounded-full" variant={item.ldapId ? 'default' : 'outline'}>
|
||||
{item.ldapId ? m.ldap() : m.local()}
|
||||
</Badge>
|
||||
{/snippet}
|
||||
|
||||
<AdvancedTable
|
||||
items={userGroups}
|
||||
onRefresh={async (o) => (userGroups = await userGroupService.list(o))}
|
||||
{requestOptions}
|
||||
columns={[
|
||||
{ label: m.friendly_name(), sortColumn: 'friendlyName' },
|
||||
{ label: m.name(), sortColumn: 'name' },
|
||||
{ label: m.user_count(), sortColumn: 'userCount' },
|
||||
...($appConfigStore.ldapEnabled ? [{ label: m.source() }] : []),
|
||||
{ label: m.actions(), hidden: true }
|
||||
]}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.friendlyName}</Table.Cell>
|
||||
<Table.Cell>{item.name}</Table.Cell>
|
||||
<Table.Cell>{item.userCount}</Table.Cell>
|
||||
{#if $appConfigStore.ldapEnabled}
|
||||
<Table.Cell>
|
||||
<Badge class="rounded-full" variant={item.ldapId ? 'default' : 'outline'}
|
||||
>{item.ldapId ? m.ldap() : m.local()}</Badge
|
||||
>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
<Table.Cell class="flex justify-end">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Ellipsis class="size-4" />
|
||||
<span class="sr-only">{m.toggle_menu()}</span>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Item onclick={() => goto(`/settings/admin/user-groups/${item.id}`)}
|
||||
><LucidePencil class="mr-2 size-4" /> {m.edit()}</DropdownMenu.Item
|
||||
>
|
||||
{#if !item.ldapId || !$appConfigStore.ldapEnabled}
|
||||
<DropdownMenu.Item
|
||||
class="text-red-500 focus:!text-red-700"
|
||||
onclick={() => deleteUserGroup(item)}
|
||||
><LucideTrash class="mr-2 size-4" />{m.delete()}</DropdownMenu.Item
|
||||
>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
id="user-group-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={userGroupService.list}
|
||||
defaultSort={{ column: 'friendlyName', direction: 'asc' }}
|
||||
{columns}
|
||||
{actions}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import AdvancedTable from '$lib/components/advanced-table.svelte';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import AdvancedTable from '$lib/components/table/advanced-table.svelte';
|
||||
import * as Avatar from '$lib/components/ui/avatar/index';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { AdvancedTableColumn } from '$lib/types/advanced-table.type';
|
||||
import type { User } from '$lib/types/user.type';
|
||||
import { onMount } from 'svelte';
|
||||
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||
|
||||
let {
|
||||
selectionDisabled = false,
|
||||
@@ -17,34 +18,63 @@
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
let users: Paginated<User> | undefined = $state();
|
||||
let requestOptions: SearchPaginationSortRequest = $state({
|
||||
sort: {
|
||||
column: 'firstName',
|
||||
direction: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
users = await userService.list(requestOptions);
|
||||
});
|
||||
const columns: AdvancedTableColumn<User>[] = [
|
||||
{ label: 'ID', column: 'id', hidden: true },
|
||||
{ label: m.profile_picture(), key: 'profilePicture', cell: ProfilePictureCell },
|
||||
{ label: m.first_name(), column: 'firstName', sortable: true, hidden: true },
|
||||
{ label: m.last_name(), column: 'lastName', sortable: true, hidden: true },
|
||||
{ label: m.display_name(), column: 'displayName', sortable: true },
|
||||
{ label: m.email(), column: 'email', sortable: true, hidden: true },
|
||||
{ label: m.username(), column: 'username', sortable: true },
|
||||
{
|
||||
label: m.role(),
|
||||
column: 'isAdmin',
|
||||
sortable: true,
|
||||
filterableValues: [
|
||||
{ label: m.admin(), value: true },
|
||||
{ label: m.user(), value: false }
|
||||
],
|
||||
value: (item) => (item.isAdmin ? m.admin() : m.user()),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
label: m.status(),
|
||||
column: 'disabled',
|
||||
cell: StatusCell,
|
||||
sortable: true,
|
||||
filterableValues: [
|
||||
{
|
||||
label: m.enabled(),
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: m.disabled(),
|
||||
value: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ label: m.ldap_id(), column: 'ldapId', hidden: true },
|
||||
{ label: m.locale(), column: 'locale', hidden: true }
|
||||
];
|
||||
</script>
|
||||
|
||||
{#if users}
|
||||
<AdvancedTable
|
||||
items={users}
|
||||
onRefresh={async (o) => (users = await userService.list(o))}
|
||||
{requestOptions}
|
||||
columns={[
|
||||
{ label: m.name(), sortColumn: 'firstName' },
|
||||
{ label: m.email(), sortColumn: 'email' }
|
||||
]}
|
||||
bind:selectedIds={selectedUserIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.displayName}</Table.Cell>
|
||||
<Table.Cell>{item.email}</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
{/if}
|
||||
{#snippet ProfilePictureCell({ item }: { item: User })}
|
||||
<Avatar.Root class="size-8">
|
||||
<Avatar.Image class="object-cover" src={cachedProfilePicture.getUrl(item.id)} />
|
||||
</Avatar.Root>
|
||||
{/snippet}
|
||||
|
||||
{#snippet StatusCell({ item }: { item: User })}
|
||||
<Badge class="rounded-full" variant={item.disabled ? 'destructive' : 'default'}>
|
||||
{item.disabled ? m.disabled() : m.enabled()}
|
||||
</Badge>
|
||||
{/snippet}
|
||||
|
||||
<AdvancedTable
|
||||
id="user-selection"
|
||||
fetchCallback={userService.list}
|
||||
defaultSort={{ column: 'firstName', direction: 'asc' }}
|
||||
bind:selectedIds={selectedUserIds}
|
||||
{selectionDisabled}
|
||||
{columns}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user