From 626d1133605bc58a8ae0be1bc03711603bb2f4ca Mon Sep 17 00:00:00 2001 From: Hakan Shehu Date: Tue, 10 Sep 2024 11:20:24 +0200 Subject: [PATCH] Implement view create mutation --- .../components/databases/database-views.tsx | 4 +- .../databases/view-create-button.tsx | 18 ++ .../databases/view-create-dialog.tsx | 206 +++++++++++++++++- .../use-board-view-create-mutation.tsx | 70 ++++++ .../use-calendar-view-create-mutation.tsx | 73 +++++++ .../use-table-view-create-mutation.tsx | 70 ++++++ desktop/src/queries/use-database-query.tsx | 46 +++- 7 files changed, 474 insertions(+), 13 deletions(-) create mode 100644 desktop/src/components/databases/view-create-button.tsx create mode 100644 desktop/src/mutations/use-board-view-create-mutation.tsx create mode 100644 desktop/src/mutations/use-calendar-view-create-mutation.tsx create mode 100644 desktop/src/mutations/use-table-view-create-mutation.tsx diff --git a/desktop/src/components/databases/database-views.tsx b/desktop/src/components/databases/database-views.tsx index c3c4dd3a..0e9ed466 100644 --- a/desktop/src/components/databases/database-views.tsx +++ b/desktop/src/components/databases/database-views.tsx @@ -3,6 +3,7 @@ import { ViewTab } from '@/components/databases/view-tab'; import { ViewActions } from '@/components/databases/view-actions'; import { ViewNode } from '@/types/databases'; import { View } from '@/components/databases/view'; +import { ViewCreateButton } from '@/components/databases/view-create-button'; interface DatabaseViewsProps { views: ViewNode[]; @@ -16,7 +17,7 @@ export const DatabaseViews = ({ views }: DatabaseViewsProps) => { return (
-
+
{views.map((view) => ( { onClick={() => setSelectedView(view)} /> ))} +
diff --git a/desktop/src/components/databases/view-create-button.tsx b/desktop/src/components/databases/view-create-button.tsx new file mode 100644 index 00000000..546f75c9 --- /dev/null +++ b/desktop/src/components/databases/view-create-button.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { ViewCreateDialog } from '@/components/databases/view-create-dialog'; +import { Icon } from '@/components/ui/icon'; + +export const ViewCreateButton = () => { + const [open, setOpen] = React.useState(false); + + return ( + + setOpen(true)} + /> + + + ); +}; diff --git a/desktop/src/components/databases/view-create-dialog.tsx b/desktop/src/components/databases/view-create-dialog.tsx index b7b2c904..08436ab5 100644 --- a/desktop/src/components/databases/view-create-dialog.tsx +++ b/desktop/src/components/databases/view-create-dialog.tsx @@ -1,5 +1,207 @@ import React from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { z } from 'zod'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Spinner } from '@/components/ui/spinner'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { cn } from '@/lib/utils'; +import { Icon } from '@/components/ui/icon'; +import { useTableViewCreateMutation } from '@/mutations/use-table-view-create-mutation'; +import { useBoardViewCreateMutation } from '@/mutations/use-board-view-create-mutation'; +import { useCalendarViewCreateMutation } from '@/mutations/use-calendar-view-create-mutation'; +import { useDatabase } from '@/contexts/database'; -export const ViewCreateDialog = () => { - return
ViewCreateDialog
; +const formSchema = z.object({ + name: z.string().min(3, 'Name must be at least 3 characters long.'), + type: z.enum(['TABLE', 'BOARD', 'CALENDAR']), +}); + +interface ViewTypeOption { + name: string; + icon: string; + type: 'TABLE' | 'BOARD' | 'CALENDAR'; +} + +const viewTypes: ViewTypeOption[] = [ + { + name: 'Table', + icon: 'table-2', + type: 'TABLE', + }, + { + name: 'Board', + icon: 'layout-column-line', + type: 'BOARD', + }, + { + name: 'Calendar', + icon: 'calendar-2-line', + type: 'CALENDAR', + }, +]; + +interface ViewCreateDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const ViewCreateDialog = ({ + open, + onOpenChange, +}: ViewCreateDialogProps) => { + const database = useDatabase(); + const tableViewCreateMutation = useTableViewCreateMutation(); + const boardViewCreateMutation = useBoardViewCreateMutation(); + const calendarViewCreateMutation = useCalendarViewCreateMutation(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: '', + type: 'TABLE', + }, + }); + + const isPending = + tableViewCreateMutation.isPending || + boardViewCreateMutation.isPending || + calendarViewCreateMutation.isPending; + + const handleCancel = () => { + form.reset(); + onOpenChange(false); + }; + + const handleSubmit = async (values: z.infer) => { + if (isPending) { + return; + } + + if (values.type === 'TABLE') { + tableViewCreateMutation.mutate( + { + databaseId: database.id, + name: values.name, + }, + { + onSuccess: () => { + form.reset(); + onOpenChange(false); + }, + }, + ); + } else if (values.type === 'BOARD') { + boardViewCreateMutation.mutate( + { + databaseId: database.id, + name: values.name, + }, + { + onSuccess: () => { + form.reset(); + onOpenChange(false); + }, + }, + ); + } else if (values.type === 'CALENDAR') { + calendarViewCreateMutation.mutate( + { + databaseId: database.id, + name: values.name, + }, + { + onSuccess: () => { + form.reset(); + onOpenChange(false); + }, + }, + ); + } + }; + + return ( + + + + Create view + + Create a new view to display your database records + + +
+ +
+ ( + + Name * + + + + + + )} + /> + ( +
+ {viewTypes.map((viewType) => ( +
field.onChange(viewType.type)} + > + +

{viewType.name}

+
+ ))} +
+ )} + /> +
+ + + + +
+ +
+
+ ); }; diff --git a/desktop/src/mutations/use-board-view-create-mutation.tsx b/desktop/src/mutations/use-board-view-create-mutation.tsx new file mode 100644 index 00000000..d39aad5e --- /dev/null +++ b/desktop/src/mutations/use-board-view-create-mutation.tsx @@ -0,0 +1,70 @@ +import { useWorkspace } from '@/contexts/workspace'; +import { AttributeTypes, NodeTypes, ViewNodeTypes } from '@/lib/constants'; +import { NeuronId } from '@/lib/id'; +import { generateNodeIndex } from '@/lib/nodes'; +import { compareString } from '@/lib/utils'; +import { useMutation } from '@tanstack/react-query'; + +interface BoardViewCreateInput { + databaseId: string; + name: string; +} + +export const useBoardViewCreateMutation = () => { + const workspace = useWorkspace(); + + return useMutation({ + mutationFn: async (input: BoardViewCreateInput) => { + const siblingsQuery = workspace.schema + .selectFrom('nodes') + .where((eb) => + eb.and([ + eb('parent_id', '=', input.databaseId), + eb('type', 'in', ViewNodeTypes), + ]), + ) + .selectAll() + .compile(); + + const result = await workspace.query(siblingsQuery); + const siblings = result.rows; + const maxIndex = + siblings.length > 0 + ? siblings.sort((a, b) => compareString(a.index, b.index))[ + siblings.length - 1 + ].index + : null; + + const boardViewId = NeuronId.generate(NeuronId.Type.BoardView); + const insertBoardViewQuery = workspace.schema + .insertInto('nodes') + .values({ + id: boardViewId, + type: NodeTypes.BoardView, + parent_id: input.databaseId, + index: generateNodeIndex(maxIndex, null), + content: null, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + const insertBoardViewNameQuery = workspace.schema + .insertInto('node_attributes') + .values({ + node_id: boardViewId, + type: AttributeTypes.Name, + key: '1', + text_value: input.name, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + await workspace.mutate([insertBoardViewQuery, insertBoardViewNameQuery]); + return boardViewId; + }, + }); +}; diff --git a/desktop/src/mutations/use-calendar-view-create-mutation.tsx b/desktop/src/mutations/use-calendar-view-create-mutation.tsx new file mode 100644 index 00000000..b956a816 --- /dev/null +++ b/desktop/src/mutations/use-calendar-view-create-mutation.tsx @@ -0,0 +1,73 @@ +import { useWorkspace } from '@/contexts/workspace'; +import { AttributeTypes, NodeTypes, ViewNodeTypes } from '@/lib/constants'; +import { NeuronId } from '@/lib/id'; +import { generateNodeIndex } from '@/lib/nodes'; +import { compareString } from '@/lib/utils'; +import { useMutation } from '@tanstack/react-query'; + +interface CalendarViewCreateInput { + databaseId: string; + name: string; +} + +export const useCalendarViewCreateMutation = () => { + const workspace = useWorkspace(); + + return useMutation({ + mutationFn: async (input: CalendarViewCreateInput) => { + const siblingsQuery = workspace.schema + .selectFrom('nodes') + .where((eb) => + eb.and([ + eb('parent_id', '=', input.databaseId), + eb('type', 'in', ViewNodeTypes), + ]), + ) + .selectAll() + .compile(); + + const result = await workspace.query(siblingsQuery); + const siblings = result.rows; + const maxIndex = + siblings.length > 0 + ? siblings.sort((a, b) => compareString(a.index, b.index))[ + siblings.length - 1 + ].index + : null; + + const calendarViewId = NeuronId.generate(NeuronId.Type.CalendarView); + const insertCalendarViewQuery = workspace.schema + .insertInto('nodes') + .values({ + id: calendarViewId, + type: NodeTypes.CalendarView, + parent_id: input.databaseId, + index: generateNodeIndex(maxIndex, null), + content: null, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + const insertCalendarViewNameQuery = workspace.schema + .insertInto('node_attributes') + .values({ + node_id: calendarViewId, + type: AttributeTypes.Name, + key: '1', + text_value: input.name, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + await workspace.mutate([ + insertCalendarViewQuery, + insertCalendarViewNameQuery, + ]); + return calendarViewId; + }, + }); +}; diff --git a/desktop/src/mutations/use-table-view-create-mutation.tsx b/desktop/src/mutations/use-table-view-create-mutation.tsx new file mode 100644 index 00000000..99e49828 --- /dev/null +++ b/desktop/src/mutations/use-table-view-create-mutation.tsx @@ -0,0 +1,70 @@ +import { useWorkspace } from '@/contexts/workspace'; +import { AttributeTypes, NodeTypes, ViewNodeTypes } from '@/lib/constants'; +import { NeuronId } from '@/lib/id'; +import { generateNodeIndex } from '@/lib/nodes'; +import { compareString } from '@/lib/utils'; +import { useMutation } from '@tanstack/react-query'; + +interface TableViewCreateInput { + databaseId: string; + name: string; +} + +export const useTableViewCreateMutation = () => { + const workspace = useWorkspace(); + + return useMutation({ + mutationFn: async (input: TableViewCreateInput) => { + const siblingsQuery = workspace.schema + .selectFrom('nodes') + .where((eb) => + eb.and([ + eb('parent_id', '=', input.databaseId), + eb('type', 'in', ViewNodeTypes), + ]), + ) + .selectAll() + .compile(); + + const result = await workspace.query(siblingsQuery); + const siblings = result.rows; + const maxIndex = + siblings.length > 0 + ? siblings.sort((a, b) => compareString(a.index, b.index))[ + siblings.length - 1 + ].index + : null; + + const tableViewId = NeuronId.generate(NeuronId.Type.TableView); + const insertTableViewQuery = workspace.schema + .insertInto('nodes') + .values({ + id: tableViewId, + type: NodeTypes.TableView, + parent_id: input.databaseId, + index: generateNodeIndex(maxIndex, null), + content: null, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + const insertTableViewNameQuery = workspace.schema + .insertInto('node_attributes') + .values({ + node_id: tableViewId, + type: AttributeTypes.Name, + key: '1', + text_value: input.name, + created_at: new Date().toISOString(), + created_by: workspace.userId, + version_id: NeuronId.generate(NeuronId.Type.Version), + }) + .compile(); + + await workspace.mutate([insertTableViewQuery, insertTableViewNameQuery]); + return tableViewId; + }, + }); +}; diff --git a/desktop/src/queries/use-database-query.tsx b/desktop/src/queries/use-database-query.tsx index 5686a3ee..cb94124b 100644 --- a/desktop/src/queries/use-database-query.tsx +++ b/desktop/src/queries/use-database-query.tsx @@ -3,6 +3,8 @@ import { SelectNodeWithAttributes } from '@/data/schemas/workspace'; import { AttributeTypes, NodeTypes, ViewNodeTypes } from '@/lib/constants'; import { mapNodeWithAttributes } from '@/lib/nodes'; import { + BoardViewNode, + CalendarViewNode, DatabaseNode, FieldDataType, FieldNode, @@ -157,7 +159,7 @@ const buildDatabaseNode = ( }; }; -export const buildFieldNode = ( +const buildFieldNode = ( node: LocalNodeWithAttributes, selectOptions: LocalNodeWithAttributes[], ): FieldNode | null => { @@ -272,9 +274,7 @@ export const buildFieldNode = ( } }; -export const buildSelectOption = ( - node: LocalNodeWithAttributes, -): SelectOptionNode => { +const buildSelectOption = (node: LocalNodeWithAttributes): SelectOptionNode => { const name = node.attributes.find( (attribute) => attribute.type === AttributeTypes.Name, )?.textValue; @@ -290,19 +290,19 @@ export const buildSelectOption = ( }; }; -export const buildViewNode = ( - node: LocalNodeWithAttributes, -): ViewNode | null => { +const buildViewNode = (node: LocalNodeWithAttributes): ViewNode | null => { if (node.type === NodeTypes.TableView) { return buildTableViewNode(node); + } else if (node.type === NodeTypes.BoardView) { + return buildBoardViewNode(node); + } else if (node.type === NodeTypes.CalendarView) { + return buildCalendarViewNode(node); } return null; }; -export const buildTableViewNode = ( - node: LocalNodeWithAttributes, -): TableViewNode => { +const buildTableViewNode = (node: LocalNodeWithAttributes): TableViewNode => { const name = node.attributes.find( (attribute) => attribute.type === AttributeTypes.Name, )?.textValue; @@ -350,3 +350,29 @@ export const buildTableViewNode = ( versionId: node.versionId, }; }; + +const buildBoardViewNode = (node: LocalNodeWithAttributes): BoardViewNode => { + const name = node.attributes.find( + (attribute) => attribute.type === AttributeTypes.Name, + )?.textValue; + + return { + id: node.id, + name: name ?? 'Unnamed', + type: 'board_view', + }; +}; + +const buildCalendarViewNode = ( + node: LocalNodeWithAttributes, +): CalendarViewNode => { + const name = node.attributes.find( + (attribute) => attribute.type === AttributeTypes.Name, + )?.textValue; + + return { + id: node.id, + name: name ?? 'Unnamed', + type: 'calendar_view', + }; +};