Add ID and Created columns to all admin pages with sortable columns
- Add ID column to all admin list pages for easy record identification - Add Created column (created_at/date_joined) to pages missing it - Implement client-side column sorting in DataTable component - Fix sorting implementation to work without getSortingRowModel export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,15 @@ const roleColors: Record<string, 'default' | 'secondary' | 'destructive'> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnDef<ManagedAdminUser>[] = [
|
const columns: ColumnDef<ManagedAdminUser>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'email',
|
accessorKey: 'email',
|
||||||
header: 'Email',
|
header: 'Email',
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export default function AppleSocialAuthPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Apple ID</TableHead>
|
<TableHead>Apple ID</TableHead>
|
||||||
<TableHead>Apple Email</TableHead>
|
<TableHead>Apple Email</TableHead>
|
||||||
@@ -202,6 +203,7 @@ export default function AppleSocialAuthPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{entry.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/users/${entry.user_id}`}
|
href={`/admin/users/${entry.user_id}`}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export default function AuthTokensPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead>Token</TableHead>
|
<TableHead>Token</TableHead>
|
||||||
@@ -201,6 +202,7 @@ export default function AuthTokensPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{token.user_id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/users/${token.user_id}`}
|
href={`/admin/users/${token.user_id}`}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export default function CompletionImagesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Preview</TableHead>
|
<TableHead>Preview</TableHead>
|
||||||
<TableHead>Task</TableHead>
|
<TableHead>Task</TableHead>
|
||||||
<TableHead>Caption</TableHead>
|
<TableHead>Caption</TableHead>
|
||||||
@@ -202,6 +203,7 @@ export default function CompletionImagesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{img.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{img.image_url ? (
|
{img.image_url ? (
|
||||||
<a href={img.image_url} target="_blank" rel="noopener noreferrer">
|
<a href={img.image_url} target="_blank" rel="noopener noreferrer">
|
||||||
|
|||||||
@@ -179,12 +179,14 @@ export default function CompletionsPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Task</TableHead>
|
<TableHead>Task</TableHead>
|
||||||
<TableHead>Residence</TableHead>
|
<TableHead>Residence</TableHead>
|
||||||
<TableHead>Completed By</TableHead>
|
<TableHead>Completed By</TableHead>
|
||||||
<TableHead>Completed At</TableHead>
|
<TableHead>Completed At</TableHead>
|
||||||
<TableHead>Cost</TableHead>
|
<TableHead>Cost</TableHead>
|
||||||
<TableHead>Photo</TableHead>
|
<TableHead>Photo</TableHead>
|
||||||
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="w-24">Actions</TableHead>
|
<TableHead className="w-24">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -212,6 +214,7 @@ export default function CompletionsPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{completion.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/completions/${completion.id}`}
|
href={`/completions/${completion.id}`}
|
||||||
@@ -295,6 +298,9 @@ export default function CompletionsPage() {
|
|||||||
<span className="text-muted-foreground">-</span>
|
<span className="text-muted-foreground">-</span>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(completion.created_at).toLocaleDateString()}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ export default function ConfirmationCodesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead>Code</TableHead>
|
<TableHead>Code</TableHead>
|
||||||
@@ -208,6 +209,7 @@ export default function ConfirmationCodesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/users/${code.user_id}`}
|
href={`/admin/users/${code.user_id}`}
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
const columns: ColumnDef<Contractor>[] = [
|
const columns: ColumnDef<Contractor>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
@@ -82,6 +91,11 @@ const columns: ColumnDef<Contractor>[] = [
|
|||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Created',
|
||||||
|
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ export default function DevicesPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Name</TableHead>
|
<TableHead>Name</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Device ID</TableHead>
|
<TableHead>Device ID</TableHead>
|
||||||
@@ -306,6 +307,7 @@ export default function DevicesPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{device.id}</TableCell>
|
||||||
<TableCell className="font-medium">{device.name || 'Unknown'}</TableCell>
|
<TableCell className="font-medium">{device.name || 'Unknown'}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{device.user_id ? (
|
{device.user_id ? (
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export default function DocumentImagesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Preview</TableHead>
|
<TableHead>Preview</TableHead>
|
||||||
<TableHead>Document</TableHead>
|
<TableHead>Document</TableHead>
|
||||||
<TableHead>Residence</TableHead>
|
<TableHead>Residence</TableHead>
|
||||||
@@ -202,6 +203,7 @@ export default function DocumentImagesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{img.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{img.image_url ? (
|
{img.image_url ? (
|
||||||
<a href={img.image_url} target="_blank" rel="noopener noreferrer">
|
<a href={img.image_url} target="_blank" rel="noopener noreferrer">
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ const documentTypeColors: Record<string, 'default' | 'secondary' | 'destructive'
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnDef<Document>[] = [
|
const columns: ColumnDef<Document>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
header: 'Title',
|
header: 'Title',
|
||||||
|
|||||||
@@ -122,11 +122,13 @@ export default function FeatureBenefitsPage() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Order</TableHead>
|
<TableHead>Order</TableHead>
|
||||||
<TableHead>Feature</TableHead>
|
<TableHead>Feature</TableHead>
|
||||||
<TableHead>Free Tier</TableHead>
|
<TableHead>Free Tier</TableHead>
|
||||||
<TableHead>Pro Tier</TableHead>
|
<TableHead>Pro Tier</TableHead>
|
||||||
<TableHead>Active</TableHead>
|
<TableHead>Active</TableHead>
|
||||||
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="w-24">Actions</TableHead>
|
<TableHead className="w-24">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -138,6 +140,7 @@ export default function FeatureBenefitsPage() {
|
|||||||
) : (
|
) : (
|
||||||
data?.data?.map((benefit) => (
|
data?.data?.map((benefit) => (
|
||||||
<TableRow key={benefit.id}>
|
<TableRow key={benefit.id}>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{benefit.id}</TableCell>
|
||||||
<TableCell>{benefit.display_order}</TableCell>
|
<TableCell>{benefit.display_order}</TableCell>
|
||||||
<TableCell className="font-medium">{benefit.feature_name}</TableCell>
|
<TableCell className="font-medium">{benefit.feature_name}</TableCell>
|
||||||
<TableCell>{benefit.free_tier_text}</TableCell>
|
<TableCell>{benefit.free_tier_text}</TableCell>
|
||||||
@@ -147,6 +150,9 @@ export default function FeatureBenefitsPage() {
|
|||||||
{benefit.is_active ? 'Active' : 'Inactive'}
|
{benefit.is_active ? 'Active' : 'Inactive'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(benefit.created_at).toLocaleDateString()}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button variant="ghost" size="icon" onClick={() => handleEdit(benefit)}>
|
<Button variant="ghost" size="icon" onClick={() => handleEdit(benefit)}>
|
||||||
|
|||||||
@@ -88,6 +88,15 @@ export default function NotificationPrefsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnDef<NotificationPreference>[] = [
|
const columns: ColumnDef<NotificationPreference>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'username',
|
accessorKey: 'username',
|
||||||
header: 'User',
|
header: 'User',
|
||||||
@@ -226,6 +235,11 @@ export default function NotificationPrefsPage() {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Created',
|
||||||
|
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ import {
|
|||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
|
||||||
const columns: ColumnDef<Notification>[] = [
|
const columns: ColumnDef<Notification>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
header: 'Title',
|
header: 'Title',
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export default function PasswordResetCodesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead>Token</TableHead>
|
<TableHead>Token</TableHead>
|
||||||
@@ -241,6 +242,7 @@ export default function PasswordResetCodesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/users/${code.user_id}`}
|
href={`/admin/users/${code.user_id}`}
|
||||||
|
|||||||
@@ -143,11 +143,13 @@ export default function PromotionsPage() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>ID</TableHead>
|
<TableHead>ID (DB)</TableHead>
|
||||||
|
<TableHead>Promotion ID</TableHead>
|
||||||
<TableHead>Title</TableHead>
|
<TableHead>Title</TableHead>
|
||||||
<TableHead>Target</TableHead>
|
<TableHead>Target</TableHead>
|
||||||
<TableHead>Date Range</TableHead>
|
<TableHead>Date Range</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="w-24">Actions</TableHead>
|
<TableHead className="w-24">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -159,6 +161,7 @@ export default function PromotionsPage() {
|
|||||||
) : (
|
) : (
|
||||||
data?.data?.map((promo) => (
|
data?.data?.map((promo) => (
|
||||||
<TableRow key={promo.id}>
|
<TableRow key={promo.id}>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{promo.id}</TableCell>
|
||||||
<TableCell><code className="text-xs bg-muted px-2 py-1 rounded">{promo.promotion_id}</code></TableCell>
|
<TableCell><code className="text-xs bg-muted px-2 py-1 rounded">{promo.promotion_id}</code></TableCell>
|
||||||
<TableCell className="font-medium">{promo.title}</TableCell>
|
<TableCell className="font-medium">{promo.title}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -178,6 +181,9 @@ export default function PromotionsPage() {
|
|||||||
<Badge variant="destructive">Inactive</Badge>
|
<Badge variant="destructive">Inactive</Badge>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(promo.created_at).toLocaleDateString()}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button variant="ghost" size="icon" onClick={() => handleEdit(promo)}>
|
<Button variant="ghost" size="icon" onClick={() => handleEdit(promo)}>
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
const columns: ColumnDef<Residence>[] = [
|
const columns: ColumnDef<Residence>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export default function ShareCodesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Code</TableHead>
|
<TableHead>Code</TableHead>
|
||||||
<TableHead>Residence</TableHead>
|
<TableHead>Residence</TableHead>
|
||||||
<TableHead>Created By</TableHead>
|
<TableHead>Created By</TableHead>
|
||||||
@@ -223,6 +224,7 @@ export default function ShareCodesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{code.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<code className="text-sm bg-muted px-2 py-1 rounded font-mono">
|
<code className="text-sm bg-muted px-2 py-1 rounded font-mono">
|
||||||
{code.code}
|
{code.code}
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ const tierColors: Record<string, 'default' | 'secondary' | 'outline'> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnDef<Subscription>[] = [
|
const columns: ColumnDef<Subscription>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'username',
|
accessorKey: 'username',
|
||||||
header: 'User',
|
header: 'User',
|
||||||
|
|||||||
@@ -239,12 +239,14 @@ export default function TaskTemplatesPage() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead className="w-16">Order</TableHead>
|
<TableHead className="w-16">Order</TableHead>
|
||||||
<TableHead>Title</TableHead>
|
<TableHead>Title</TableHead>
|
||||||
<TableHead>Category</TableHead>
|
<TableHead>Category</TableHead>
|
||||||
<TableHead>Frequency</TableHead>
|
<TableHead>Frequency</TableHead>
|
||||||
<TableHead>iOS Icon</TableHead>
|
<TableHead>iOS Icon</TableHead>
|
||||||
<TableHead>Active</TableHead>
|
<TableHead>Active</TableHead>
|
||||||
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="w-32">Actions</TableHead>
|
<TableHead className="w-32">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -256,6 +258,7 @@ export default function TaskTemplatesPage() {
|
|||||||
) : (
|
) : (
|
||||||
data?.data?.map((template) => (
|
data?.data?.map((template) => (
|
||||||
<TableRow key={template.id}>
|
<TableRow key={template.id}>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{template.id}</TableCell>
|
||||||
<TableCell className="text-muted-foreground">{template.display_order}</TableCell>
|
<TableCell className="text-muted-foreground">{template.display_order}</TableCell>
|
||||||
<TableCell className="font-medium max-w-xs">
|
<TableCell className="font-medium max-w-xs">
|
||||||
<div className="truncate">{template.title}</div>
|
<div className="truncate">{template.title}</div>
|
||||||
@@ -275,6 +278,9 @@ export default function TaskTemplatesPage() {
|
|||||||
{template.is_active ? 'Active' : 'Inactive'}
|
{template.is_active ? 'Active' : 'Inactive'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(template.created_at).toLocaleDateString()}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
const columns: ColumnDef<Task>[] = [
|
const columns: ColumnDef<Task>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
header: 'Title',
|
header: 'Title',
|
||||||
@@ -81,6 +90,11 @@ const columns: ColumnDef<Task>[] = [
|
|||||||
return <Badge variant="default">Active</Badge>;
|
return <Badge variant="default">Active</Badge>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Created',
|
||||||
|
cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export default function UserProfilesPage() {
|
|||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead>Phone</TableHead>
|
<TableHead>Phone</TableHead>
|
||||||
@@ -202,6 +203,7 @@ export default function UserProfilesPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm text-muted-foreground">{profile.id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/users/${profile.user_id}`}
|
href={`/admin/users/${profile.user_id}`}
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
const columns: ColumnDef<User>[] = [
|
const columns: ColumnDef<User>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="font-mono text-sm text-muted-foreground">
|
||||||
|
{row.original.id}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'username',
|
accessorKey: 'username',
|
||||||
header: 'Username',
|
header: 'Username',
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
flexRender,
|
flexRender,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
|
SortingState,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -17,6 +20,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { DataTablePagination } from './data-table-pagination';
|
import { DataTablePagination } from './data-table-pagination';
|
||||||
import { DataTableToolbar } from './data-table-toolbar';
|
import { DataTableToolbar } from './data-table-toolbar';
|
||||||
|
|
||||||
@@ -52,6 +56,37 @@ export function DataTable<TData, TValue>({
|
|||||||
onRowSelectionChange,
|
onRowSelectionChange,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
|
||||||
|
// Sort data client-side based on sorting state
|
||||||
|
const sortedData = React.useMemo(() => {
|
||||||
|
if (sorting.length === 0) return data;
|
||||||
|
|
||||||
|
const [sort] = sorting;
|
||||||
|
const { id, desc } = sort;
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const aValue = (a as Record<string, unknown>)[id];
|
||||||
|
const bValue = (b as Record<string, unknown>)[id];
|
||||||
|
|
||||||
|
// Handle null/undefined
|
||||||
|
if (aValue == null && bValue == null) return 0;
|
||||||
|
if (aValue == null) return desc ? -1 : 1;
|
||||||
|
if (bValue == null) return desc ? 1 : -1;
|
||||||
|
|
||||||
|
// Compare values
|
||||||
|
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||||
|
return desc ? bValue - aValue : aValue - bValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
if (aStr < bStr) return desc ? 1 : -1;
|
||||||
|
if (aStr > bStr) return desc ? -1 : 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}, [data, sorting]);
|
||||||
|
|
||||||
// Add selection column if enabled
|
// Add selection column if enabled
|
||||||
const tableColumns = React.useMemo(() => {
|
const tableColumns = React.useMemo(() => {
|
||||||
@@ -84,19 +119,22 @@ export function DataTable<TData, TValue>({
|
|||||||
}, [columns, enableRowSelection]);
|
}, [columns, enableRowSelection]);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data: sortedData,
|
||||||
columns: tableColumns,
|
columns: tableColumns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
pageCount: Math.ceil(totalCount / pageSize),
|
pageCount: Math.ceil(totalCount / pageSize),
|
||||||
state: {
|
state: {
|
||||||
rowSelection,
|
rowSelection,
|
||||||
|
sorting,
|
||||||
pagination: {
|
pagination: {
|
||||||
pageIndex: page - 1,
|
pageIndex: page - 1,
|
||||||
pageSize,
|
pageSize,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onRowSelectionChange: setRowSelection,
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onSortingChange: setSorting,
|
||||||
enableRowSelection,
|
enableRowSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,12 +165,31 @@ export function DataTable<TData, TValue>({
|
|||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<TableHead key={header.id}>
|
<TableHead key={header.id}>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder ? null : header.column.getCanSort() ? (
|
||||||
? null
|
<Button
|
||||||
: flexRender(
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||||
|
onClick={() => header.column.toggleSorting()}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
header.column.columnDef.header,
|
header.column.columnDef.header,
|
||||||
header.getContext()
|
header.getContext()
|
||||||
)}
|
)}
|
||||||
|
{header.column.getIsSorted() === 'asc' ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : header.column.getIsSorted() === 'desc' ? (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowUpDown className="ml-2 h-4 w-4 opacity-50" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)
|
||||||
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -188,5 +245,3 @@ export function DataTable<TData, TValue>({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|||||||
Reference in New Issue
Block a user