Skip to content

Commit

Permalink
refactor(ui): Edit admin overview page
Browse files Browse the repository at this point in the history
Remove the organizations table, as it currently doesn't provide
any additional value. Add statistics cards for ORT runs that can
later link to the corresponding admin sub-page after it has been
added in a future PR.

Signed-off-by: Johanna Lamppu <johanna.lamppu@doubleopen.org>
  • Loading branch information
lamppu authored and mmurto committed Oct 18, 2024
1 parent 54cfa12 commit df69a14
Showing 1 changed file with 96 additions and 202 deletions.
298 changes: 96 additions & 202 deletions ui/src/routes/_layout/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,219 +18,113 @@
*/

import { createFileRoute, Link } from '@tanstack/react-router';
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from '@tanstack/react-table';
import {
ChevronDownIcon,
ChevronsUpDownIcon,
ChevronUpIcon,
} from 'lucide-react';
import { useState } from 'react';
import { AudioWaveform, List, ListVideo, Loader2 } from 'lucide-react';

import { useOrganizationsServiceGetOrganizationsSuspense } from '@/api/queries/suspense';
import { Organization } from '@/api/requests';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';

const columns: ColumnDef<Organization>[] = [
{
accessorKey: 'id',
header: ({ column }) => {
return (
<Button
variant='ghost'
className='px-0'
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
<Label className='cursor-pointer font-bold'>Id</Label>
{column.getIsSorted() === 'desc' ? (
<ChevronDownIcon className='ml-2 h-4 w-4' />
) : column.getIsSorted() === 'asc' ? (
<ChevronUpIcon className='ml-2 h-4 w-4' />
) : (
<ChevronsUpDownIcon className='ml-2 h-4 w-4' />
)}
</Button>
);
},
cell: ({ row }) => row.original.id,
},
{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant='ghost'
className='px-0'
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
<Label className='cursor-pointer font-bold'>Name</Label>
{column.getIsSorted() === 'desc' ? (
<ChevronDownIcon className='ml-2 h-4 w-4' />
) : column.getIsSorted() === 'asc' ? (
<ChevronUpIcon className='ml-2 h-4 w-4' />
) : (
<ChevronsUpDownIcon className='ml-2 h-4 w-4' />
)}
</Button>
);
},
cell: ({ row }) => (
<Link
className='font-semibold text-blue-400 hover:underline'
to='/organizations/$orgId'
params={{ orgId: row.original.id.toString() }}
>
{row.original.name}
</Link>
),
},
];

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}

const DataTable = <TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) => {
const [sorting, setSorting] = useState<SortingState>([
{ id: 'id', desc: false },
]);
useOrganizationsServiceGetOrganizations,
useRunsServiceGetOrtRuns,
} from '@/api/queries';
import { StatisticsCard } from '@/components/statistics-card';
import { ToastError } from '@/components/toast-error';
import { toast } from '@/lib/toast';

const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
const OverviewContent = () => {
const {
data: orgs,
isLoading: orgsIsLoading,
error: orgIsError,
} = useOrganizationsServiceGetOrganizations({
limit: 1,
});

return (
<div>
<div className='rounded-md border'>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className='h-24 text-center'
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className='flex items-center justify-end space-x-2 py-4'>
<Button
variant='outline'
size='sm'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant='outline'
size='sm'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
);
};
const {
data: runs,
isLoading: runsIsLoading,
error: runsIsError,
} = useRunsServiceGetOrtRuns({
limit: 1,
});

const OverviewContent = () => {
const { data } = useOrganizationsServiceGetOrganizationsSuspense({
limit: 1000,
const {
data: activeRuns,
isLoading: activeRunsIsLoading,
error: activeRunsIsError,
} = useRunsServiceGetOrtRuns({
limit: 1,
status: 'active',
});

if (orgIsError || runsIsError || activeRunsIsError) {
toast.error('Unable to load data', {
description: (
<ToastError error={orgIsError || runsIsError || activeRunsIsError} />
),
duration: Infinity,
cancel: {
label: 'Dismiss',
onClick: () => {},
},
});
}

return (
<div className='space-y-4'>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4'>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm'>Organizations count</CardTitle>
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{data.data.length}</div>
<p className='hidden text-xs text-muted-foreground'>total</p>
</CardContent>
</Card>
</div>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-7'>
<Card className='col-span-4'>
<CardHeader>
<CardTitle>Organizations</CardTitle>
</CardHeader>
<CardContent>
<div className='w-full'>
<DataTable columns={columns} data={data.data} />
</div>
</CardContent>
</Card>
<Link to='/'>
<StatisticsCard
title='Organizations'
icon={() => <List className='h-4 w-4' />}
value={
orgsIsLoading ? (
<>
<span className='sr-only'>Loading...</span>
<Loader2 size={16} className='animate-spin' />
</>
) : orgIsError ? (
<span className='text-sm text-red-500'>
Error fetching data
</span>
) : (
orgs?.pagination.totalCount
)
}
className='h-full hover:bg-muted/50'
/>
</Link>
<StatisticsCard
title='ORT runs'
icon={() => <ListVideo className='h-4 w-4' />}
value={
runsIsLoading ? (
<>
<span className='sr-only'>Loading...</span>
<Loader2 size={16} className='animate-spin' />
</>
) : runsIsError ? (
<span className='text-sm text-red-500'>Error fetching data</span>
) : (
runs?.pagination.totalCount
)
}
className='h-full'
/>
<StatisticsCard
title='Active runs'
icon={() => <AudioWaveform className='h-4 w-4' />}
value={
activeRunsIsLoading ? (
<>
<span className='sr-only'>Loading...</span>
<Loader2 size={16} className='animate-spin' />
</>
) : activeRunsIsError ? (
<span className='text-sm text-red-500'>Error fetching data</span>
) : (
activeRuns?.pagination.totalCount
)
}
className='h-full'
/>
</div>
</div>
);
Expand Down

0 comments on commit df69a14

Please sign in to comment.