diff options
Diffstat (limited to 'components/client-table-v2/client-table-save-view.tsx')
| -rw-r--r-- | components/client-table-v2/client-table-save-view.tsx | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/components/client-table-v2/client-table-save-view.tsx b/components/client-table-v2/client-table-save-view.tsx new file mode 100644 index 00000000..73935d00 --- /dev/null +++ b/components/client-table-v2/client-table-save-view.tsx @@ -0,0 +1,185 @@ +"use client"; + +import * as React from "react"; +import { Table } from "@tanstack/react-table"; +import { useSession } from "next-auth/react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Bookmark, Save, Trash2 } from "lucide-react"; +import { + getUserCustomSettings, + saveUserCustomSetting, + deleteUserCustomSetting, +} from "@/actions/user-custom-data"; +import { toast } from "sonner"; + +interface ClientTableSaveViewProps<TData> { + table: Table<TData>; + tableKey: string; +} + +export function ClientTableSaveView<TData>({ + table, + tableKey, +}: ClientTableSaveViewProps<TData>) { + const { data: session } = useSession(); + const [savedViews, setSavedViews] = React.useState<{ id: string; customSettingName: string; customSetting: Record<string, any> }[]>([]); + const [isSaveDialogOpen, setIsSaveDialogOpen] = React.useState(false); + const [newViewName, setNewViewName] = React.useState(""); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchSettings = React.useCallback(async () => { + const userIdVal = session?.user?.id; + if (!userIdVal) return; + + const userId = Number(userIdVal); + if (isNaN(userId)) return; + + const res = await getUserCustomSettings(tableKey, userId); + if (res.success && res.data) { + // @ts-ignore - data from DB might need casting + setSavedViews(res.data); + } + }, [session, tableKey]); + + React.useEffect(() => { + if (session) { + fetchSettings(); + } + }, [fetchSettings, session]); + + const handleSaveView = async () => { + const userIdVal = session?.user?.id; + if (!newViewName.trim() || !userIdVal) return; + const userId = Number(userIdVal); + if (isNaN(userId)) return; + + setIsLoading(true); + const state = table.getState(); + const settingToSave = { + sorting: state.sorting, + columnFilters: state.columnFilters, + globalFilter: state.globalFilter, + columnVisibility: state.columnVisibility, + columnPinning: state.columnPinning, + columnOrder: state.columnOrder, + grouping: state.grouping, + pagination: { pageSize: state.pagination.pageSize }, + }; + + const res = await saveUserCustomSetting(userId, tableKey, newViewName, settingToSave); + setIsLoading(false); + + if (res.success) { + toast.success("View saved successfully"); + setIsSaveDialogOpen(false); + setNewViewName(""); + fetchSettings(); + } else { + toast.error("Failed to save view"); + } + }; + + const handleLoadView = (setting: { customSetting: Record<string, any> | unknown; customSettingName: string }) => { + const s = setting.customSetting as Record<string, any>; + if (!s) return; + + if (s.sorting) table.setSorting(s.sorting); + if (s.columnFilters) table.setColumnFilters(s.columnFilters); + if (s.globalFilter !== undefined) table.setGlobalFilter(s.globalFilter); + if (s.columnVisibility) table.setColumnVisibility(s.columnVisibility); + if (s.columnPinning) table.setColumnPinning(s.columnPinning); + if (s.columnOrder) table.setColumnOrder(s.columnOrder); + if (s.grouping) table.setGrouping(s.grouping); + if (s.pagination?.pageSize) table.setPageSize(s.pagination.pageSize); + + toast.success(`View "${setting.customSettingName}" loaded`); + }; + + const handleDeleteView = async (e: React.MouseEvent, id: string) => { + e.stopPropagation(); + if (!confirm("Are you sure you want to delete this view?")) return; + + const res = await deleteUserCustomSetting(id); + if (res.success) { + toast.success("View deleted"); + fetchSettings(); + } else { + toast.error("Failed to delete view"); + } + }; + + if (!session) return null; + + return ( + <> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button variant="outline" size="sm" className="ml-2 hidden h-8 lg:flex"> + <Bookmark className="mr-2 h-4 w-4" /> + Views + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-[200px]"> + <DropdownMenuLabel>Saved Views</DropdownMenuLabel> + <DropdownMenuSeparator /> + {savedViews.length === 0 ? ( + <div className="p-2 text-sm text-muted-foreground text-center">No saved views</div> + ) : ( + savedViews.map((view) => ( + <DropdownMenuItem key={view.id} onClick={() => handleLoadView(view)} className="flex justify-between cursor-pointer"> + <span className="truncate flex-1">{view.customSettingName}</span> + <Button variant="ghost" size="icon" className="h-4 w-4" onClick={(e) => handleDeleteView(e, view.id)}> + <Trash2 className="h-3 w-3 text-destructive" /> + </Button> + </DropdownMenuItem> + )) + )} + <DropdownMenuSeparator /> + <DropdownMenuItem onClick={() => setIsSaveDialogOpen(true)} className="cursor-pointer"> + <Save className="mr-2 h-4 w-4" /> + Save Current View + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + + <Dialog open={isSaveDialogOpen} onOpenChange={setIsSaveDialogOpen}> + <DialogContent> + <DialogHeader> + <DialogTitle>Save View</DialogTitle> + <DialogDescription> + Save the current table configuration as a preset. + </DialogDescription> + </DialogHeader> + <div className="grid gap-4 py-4"> + <Input + placeholder="View Name" + value={newViewName} + onChange={(e) => setNewViewName(e.target.value)} + /> + </div> + <DialogFooter> + <Button variant="outline" onClick={() => setIsSaveDialogOpen(false)}>Cancel</Button> + <Button onClick={handleSaveView} disabled={isLoading}>Save</Button> + </DialogFooter> + </DialogContent> + </Dialog> + </> + ); +} |
