added data table, formatter

revert context on __root beforeLoad
refactor project structure
refactor role badge
dynamic nav menu
This commit is contained in:
2026-01-14 09:35:46 +07:00
parent a44fa70500
commit edb4ebe11c
45 changed files with 1519 additions and 149 deletions

View File

@@ -7,5 +7,11 @@ export const Route = createFileRoute('/(app)/(auth)/dashboard')({
});
function RouteComponent() {
return <div>Hello "dashboard"!</div>;
return (
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card *:data-[slot=card]:bg-linear-to-br *:data-[slot=card]:shadow-xs grid grid-cols-1 @xl/main:grid-cols-2 @5xl/main:grid-cols-3 gap-4">
Hello Dashboard!
</div>
</div>
);
}

View File

@@ -0,0 +1,110 @@
import { logColumns } from '@/components/audit/audit-columns';
import DataTable from '@/components/DataTable';
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle } from '@/components/ui/card';
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from '@/components/ui/input-group';
import { Skeleton } from '@/components/ui/skeleton';
import useDebounced from '@/hooks/use-debounced';
import { m } from '@/paraglide/messages';
import { auditQueries } from '@/service/queries';
import {
CircuitryIcon,
MagnifyingGlassIcon,
XIcon,
} from '@phosphor-icons/react';
import { useQuery } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { useState } from 'react';
export const Route = createFileRoute('/(app)/(auth)/logs')({
component: RouteComponent,
staticData: { breadcrumb: () => m.nav_log() },
});
function RouteComponent() {
const [page, setPage] = useState(1);
const [pageLimit, setPageLimit] = useState(10);
const [searchKeyword, setSearchKeyword] = useState('');
const debouncedSearch = useDebounced(searchKeyword, 500);
const { data, isLoading } = useQuery(
auditQueries.list({
page: page,
limit: pageLimit,
keyword: debouncedSearch,
}),
);
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchKeyword(e.target.value);
setPage(1);
};
const onClearSearch = () => {
setSearchKeyword('');
};
if (isLoading) {
return (
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
<div className="flex flex-col gap-4">
<Skeleton className="h-20 w-full" />
<Skeleton className="h-130 w-full" />
</div>
</div>
);
}
return (
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card *:data-[slot=card]:bg-linear-to-br *:data-[slot=card]:shadow-xs flex flex-col gap-4">
<Card>
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<CircuitryIcon size={24} />
{m.logs_page_ui_title()}
</CardTitle>
</CardHeader>
</Card>
<div className="flex">
<InputGroup className="w-70">
<InputGroupInput
id="keywords"
placeholder="Search...."
value={searchKeyword}
onChange={onSearchChange}
/>
<InputGroupAddon>
<MagnifyingGlassIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
{searchKeyword !== '' && (
<Button
variant="ghost"
size="icon-sm"
className="rounded-full"
onClick={onClearSearch}
>
<XIcon />
</Button>
)}
</InputGroupAddon>
</InputGroup>
</div>
{data && (
<DataTable
data={data.result || []}
columns={logColumns}
page={page}
setPage={setPage}
limit={pageLimit}
setLimit={setPageLimit}
pagination={data.pagination}
/>
)}
</div>
</div>
);
}

View File

@@ -1,11 +1,11 @@
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
export const Route = createFileRoute('/(app)/(auth)')({
// beforeLoad: async ({ context }) => {
// if (!context.userSession) {
// throw redirect({ to: '/sign-in' });
// }
// },
beforeLoad: async ({ context }) => {
if (!context.session) {
throw redirect({ to: '/sign-in' });
}
},
component: RouteComponent,
});

View File

@@ -7,5 +7,11 @@ export const Route = createFileRoute('/(app)/')({
});
function App() {
return <div className="min-h-screen bg-linear-to-b ">Home</div>;
return (
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card *:data-[slot=card]:bg-linear-to-br *:data-[slot=card]:shadow-xs grid grid-cols-1 @xl/main:grid-cols-2 @5xl/main:grid-cols-3 gap-4">
Home
</div>
</div>
);
}