diff --git a/desktop/src/components/databases/tables/cells/table-view-boolean-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-boolean-cell.tsx index ca695312..067d7656 100644 --- a/desktop/src/components/databases/tables/cells/table-view-boolean-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-boolean-cell.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { Checkbox } from '@/components/ui/checkbox'; import { BooleanFieldNode, RecordNode } from '@/types/databases'; -import { useUpdateBooleanFieldValueMutation } from '@/mutations/use-update-boolean-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; const getBooleanValue = ( record: RecordNode, @@ -24,8 +25,12 @@ export const TableViewBooleanCell = ({ record, field, }: TableViewBooleanCellProps) => { - const { mutate, isPending } = useUpdateBooleanFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; const canEdit = true; const [input, setInput] = React.useState( @@ -47,11 +52,23 @@ export const TableViewBooleanCell = ({ if (typeof e === 'boolean') { setInput(e.valueOf()); - mutate({ - recordId: record.id, - fieldId: field.id, - value: e.valueOf(), - }); + const checked = e.valueOf(); + if (checked) { + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + numberValue: 1, + textValue: null, + foreignNodeId: null, + }); + } else { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); + } } }} /> diff --git a/desktop/src/components/databases/tables/cells/table-view-email-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-email-cell.tsx index d172c0f3..0b2de2b0 100644 --- a/desktop/src/components/databases/tables/cells/table-view-email-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-email-cell.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import isHotkey from 'is-hotkey'; import { RecordNode, EmailFieldNode } from '@/types/databases'; -import { useUpdateEmailFieldValueMutation } from '@/mutations/use-update-email-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; +import { SmartTextInput } from '@/components/ui/smart-text-input'; const getEmailValue = (record: RecordNode, field: EmailFieldNode): string => { const attribute = record.attributes.find((attr) => attr.type === field.id); @@ -17,38 +18,37 @@ export const TableViewEmailCell = ({ record, field, }: TableViewEmailCellProps) => { - const { mutate, isPending } = useUpdateEmailFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); const canEdit = true; - - const [text, setText] = React.useState( - getEmailValue(record, field) ?? '', - ); - - React.useEffect(() => { - setText(getEmailValue(record, field) ?? ''); - }, [record.versionId]); - - const saveIfChanged = (current: string, previous: string | null) => { - if (current.length && current !== previous) { - mutate({ - recordId: record.id, - fieldId: field.id, - value: current, - }); - } - }; + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; return ( - setText(e.target.value)} - onBlur={() => saveIfChanged(text, getEmailValue(record, field))} - onKeyDown={(e) => { - if (isHotkey('enter', e)) { - saveIfChanged(text, getEmailValue(record, field)); - e.preventDefault(); + onChange={(newValue) => { + if (isPending) return; + if (!canEdit) return; + + if (newValue === null || newValue === '') { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); + } else { + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + textValue: newValue, + numberValue: null, + foreignNodeId: null, + }); } }} className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text" diff --git a/desktop/src/components/databases/tables/cells/table-view-multi-select-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-multi-select-cell.tsx index 905ecf10..b54ca2e6 100644 --- a/desktop/src/components/databases/tables/cells/table-view-multi-select-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-multi-select-cell.tsx @@ -7,7 +7,8 @@ import { PopoverContent, } from '@/components/ui/popover'; import { SelectFieldOptions } from '@/components/databases/fields/select-field-options'; -import { useUpdateMultiSelectFieldValueMutation } from '@/mutations/use-update-multi-select-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; const getMultiSelectValues = ( record: RecordNode, @@ -26,7 +27,13 @@ export const TableViewMultiSelectCell = ({ record, field, }: TableViewMultiSelectCellProps) => { - const { mutate, isPending } = useUpdateMultiSelectFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); + + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; + const [open, setOpen] = React.useState(false); const [selectedValues, setSelectedValues] = React.useState( getMultiSelectValues(record, field), @@ -63,19 +70,20 @@ export const TableViewMultiSelectCell = ({ if (selectedValues.includes(id)) { setSelectedValues(selectedValues.filter((v) => v !== id)); - mutate({ - recordId: record.id, - fieldId: field.id, - selectOptionId: id, - add: false, + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: id, }); } else { setSelectedValues([...selectedValues, id]); - mutate({ - recordId: record.id, - fieldId: field.id, - selectOptionId: id, - add: true, + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: id, + foreignNodeId: id, + numberValue: null, + textValue: null, }); } }} diff --git a/desktop/src/components/databases/tables/cells/table-view-number-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-number-cell.tsx index 00f121dc..aef409d7 100644 --- a/desktop/src/components/databases/tables/cells/table-view-number-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-number-cell.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import isHotkey from 'is-hotkey'; import { RecordNode } from '@/types/databases'; import { NumberFieldNode } from '@/types/databases'; -import { useUpdateNumberFieldValueMutation } from '@/mutations/use-update-number-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; +import { SmartNumberInput } from '@/components/ui/smart-number-input'; const getNumberValue = ( record: RecordNode, @@ -21,55 +22,44 @@ export const TableViewNumberCell = ({ record, field, }: TableViewNumberCellProps) => { - const { mutate, isPending } = useUpdateNumberFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); const canEdit = true; - - const [input, setInput] = React.useState( - getNumberValue(record, field)?.toString() ?? '', - ); - - React.useEffect(() => { - setInput(getNumberValue(record, field)?.toString() ?? ''); - }, [record.versionId]); - - const saveIfChanged = (current: number | null, previous: number | null) => { - if (current !== previous) { - mutate({ - recordId: record.id, - fieldId: field.id, - value: current, - }); - } - }; + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; return ( - setInput(e.target.value)} - onBlur={() => { - const number = Number(input); - if (Number.isNaN(number)) { - setInput(''); - saveIfChanged(null, getNumberValue(record, field)); + onChange={(newValue) => { + if (isPending) return; + if (!canEdit) return; + + if (newValue === getNumberValue(record, field)) { + return; + } + + if (newValue === null) { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); } else { - saveIfChanged(number, getNumberValue(record, field)); - } - }} - onKeyDown={(e) => { - if (isHotkey('enter', e)) { - const number = Number(input); - if (Number.isNaN(number)) { - setInput(''); - saveIfChanged(null, getNumberValue(record, field)); - } else { - saveIfChanged(number, getNumberValue(record, field)); - } - e.preventDefault(); + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + numberValue: newValue, + textValue: null, + foreignNodeId: null, + }); } }} + className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text" /> ); }; diff --git a/desktop/src/components/databases/tables/cells/table-view-phone-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-phone-cell.tsx index 95436b7f..cd172c0a 100644 --- a/desktop/src/components/databases/tables/cells/table-view-phone-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-phone-cell.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import isHotkey from 'is-hotkey'; import { RecordNode, PhoneFieldNode } from '@/types/databases'; -import { useUpdatePhoneFieldValueMutation } from '@/mutations/use-update-phone-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; +import { SmartTextInput } from '@/components/ui/smart-text-input'; const getPhoneValue = (record: RecordNode, field: PhoneFieldNode): string => { const attribute = record.attributes.find((attr) => attr.type === field.id); @@ -17,38 +18,41 @@ export const TableViewPhoneCell = ({ record, field, }: TableViewPhoneCellProps) => { - const { mutate, isPending } = useUpdatePhoneFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); const canEdit = true; - - const [text, setText] = React.useState( - getPhoneValue(record, field) ?? '', - ); - - React.useEffect(() => { - setText(getPhoneValue(record, field) ?? ''); - }, [record.versionId]); - - const saveIfChanged = (current: string, previous: string | null) => { - if (current.length && current !== previous) { - mutate({ - recordId: record.id, - fieldId: field.id, - value: current, - }); - } - }; + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; return ( - setText(e.target.value)} - onBlur={() => saveIfChanged(text, getPhoneValue(record, field))} - onKeyDown={(e) => { - if (isHotkey('enter', e)) { - saveIfChanged(text, getPhoneValue(record, field)); - e.preventDefault(); + onChange={(newValue) => { + if (isPending) return; + if (!canEdit) return; + + if (newValue === getPhoneValue(record, field)) { + return; + } + + if (newValue === null || newValue === '') { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); + } else { + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + numberValue: 1, + textValue: newValue, + foreignNodeId: null, + }); } }} className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text" diff --git a/desktop/src/components/databases/tables/cells/table-view-select-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-select-cell.tsx index f993eb51..4f63918b 100644 --- a/desktop/src/components/databases/tables/cells/table-view-select-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-select-cell.tsx @@ -7,7 +7,8 @@ import { PopoverContent, } from '@/components/ui/popover'; import { SelectFieldOptions } from '@/components/databases/fields/select-field-options'; -import { useUpdateSelectFieldValueMutation } from '@/mutations/use-update-select-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; const getSelectValue = ( record: RecordNode, @@ -26,7 +27,13 @@ export const TableViewSelectCell = ({ record, field, }: TableViewSelectCellProps) => { - const { mutate, isPending } = useUpdateSelectFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); + + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; + const [open, setOpen] = React.useState(false); const [selectedValue, setSelectedValue] = React.useState( getSelectValue(record, field) ?? '', @@ -65,20 +72,20 @@ export const TableViewSelectCell = ({ if (selectedValue === id) { setSelectedValue(''); - mutate({ - recordId: record.id, - fieldId: field.id, - selectOptionId: id, - add: false, + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', }); } else { - setSelectedValue(id); - mutate( + upsertNodeAttribute( { - recordId: record.id, - fieldId: field.id, - selectOptionId: id, - add: true, + nodeId: record.id, + type: field.id, + key: '1', + foreignNodeId: id, + textValue: null, + numberValue: null, }, { onSuccess: () => { @@ -87,6 +94,7 @@ export const TableViewSelectCell = ({ }, ); } + setSelectedValue(id); }} /> diff --git a/desktop/src/components/databases/tables/cells/table-view-text-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-text-cell.tsx index 764f0265..c73dc8eb 100644 --- a/desktop/src/components/databases/tables/cells/table-view-text-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-text-cell.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import isHotkey from 'is-hotkey'; import { RecordNode, TextFieldNode } from '@/types/databases'; -import { useUpdateTextFieldValueMutation } from '@/mutations/use-update-text-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; +import { SmartTextInput } from '@/components/ui/smart-text-input'; const getTextValue = (record: RecordNode, field: TextFieldNode): string => { const attribute = record.attributes.find((attr) => attr.type === field.id); @@ -17,38 +18,41 @@ export const TableViewTextCell = ({ record, field, }: TableViewTextCellProps) => { - const { mutate, isPending } = useUpdateTextFieldValueMutation(); + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); const canEdit = true; - - const [text, setText] = React.useState( - getTextValue(record, field) ?? '', - ); - - React.useEffect(() => { - setText(getTextValue(record, field) ?? ''); - }, [record.versionId]); - - const saveIfChanged = (current: string, previous: string | null) => { - if (current.length && current !== previous) { - mutate({ - recordId: record.id, - fieldId: field.id, - value: current, - }); - } - }; + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; return ( - setText(e.target.value)} - onBlur={() => saveIfChanged(text, getTextValue(record, field))} - onKeyDown={(e) => { - if (isHotkey('enter', e)) { - saveIfChanged(text, getTextValue(record, field)); - e.preventDefault(); + onChange={(newValue) => { + if (isPending) return; + if (!canEdit) return; + + if (newValue === getTextValue(record, field)) { + return; + } + + if (newValue === null || newValue === '') { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); + } else { + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + numberValue: 1, + textValue: newValue, + foreignNodeId: null, + }); } }} className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text" diff --git a/desktop/src/components/databases/tables/cells/table-view-url-cell.tsx b/desktop/src/components/databases/tables/cells/table-view-url-cell.tsx index e30d946d..510de3a9 100644 --- a/desktop/src/components/databases/tables/cells/table-view-url-cell.tsx +++ b/desktop/src/components/databases/tables/cells/table-view-url-cell.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import isHotkey from 'is-hotkey'; import { RecordNode, UrlFieldNode } from '@/types/databases'; import { cn, isValidUrl } from '@/lib/utils'; import { @@ -8,7 +7,9 @@ import { HoverCardTrigger, } from '@/components/ui/hover-card'; import { Icon } from '@/components/ui/icon'; -import { useUpdateUrlFieldValueMutation } from '@/mutations/use-update-url-field-value-mutation'; +import { useNodeAttributeUpsertMutation } from '@/mutations/use-node-attribute-upsert-mutation'; +import { useNodeAttributeDeleteMutation } from '@/mutations/use-node-attribute-delete-mutation'; +import { SmartTextInput } from '@/components/ui/smart-text-input'; const getUrlValue = (record: RecordNode, field: UrlFieldNode): string => { const attribute = record.attributes.find((attr) => attr.type === field.id); @@ -18,56 +19,51 @@ const getUrlValue = (record: RecordNode, field: UrlFieldNode): string => { interface TableViewUrlCellProps { record: RecordNode; field: UrlFieldNode; - className?: string; } -export const TableViewUrlCell = ({ - record, - field, - className, -}: TableViewUrlCellProps) => { - const { mutate, isPending } = useUpdateUrlFieldValueMutation(); +export const TableViewUrlCell = ({ record, field }: TableViewUrlCellProps) => { + const { mutate: upsertNodeAttribute, isPending: isUpsertingNodeAttribute } = + useNodeAttributeUpsertMutation(); + const { mutate: deleteNodeAttribute, isPending: isDeletingNodeAttribute } = + useNodeAttributeDeleteMutation(); const canEdit = true; - - const [text, setText] = React.useState( - getUrlValue(record, field) ?? '', - ); - - React.useEffect(() => { - setText(getUrlValue(record, field) ?? ''); - }, [record.versionId]); - - const saveIfChanged = (current: string, previous: string | null) => { - if (current.length && current !== previous) { - mutate({ - recordId: record.id, - fieldId: field.id, - value: current, - }); - } - }; - + const isPending = isUpsertingNodeAttribute || isDeletingNodeAttribute; + const text = getUrlValue(record, field); const isUrl = text.length > 0 && isValidUrl(text); return ( - setText(e.target.value)} - onBlur={() => saveIfChanged(text, getUrlValue(record, field))} - onKeyDown={(e) => { - if (isHotkey('enter', e)) { - saveIfChanged(text, getUrlValue(record, field)); - e.preventDefault(); + onChange={(newValue) => { + if (isPending) return; + if (!canEdit) return; + + if (newValue === text) { + return; + } + + if (newValue === null || newValue === '') { + deleteNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + }); + } else { + upsertNodeAttribute({ + nodeId: record.id, + type: field.id, + key: '1', + numberValue: 1, + textValue: newValue, + foreignNodeId: null, + }); } }} - className={cn( - 'flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text', - className, - )} + className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text" /> (({ value, onChange, className, min, max, step = 1, ...props }, ref) => { - const [localValue, setLocalValue] = React.useState(value?.toString() ?? ''); - const initialValue = React.useRef(value?.toString() ?? ''); +>( + ( + { value, onChange, className, min, max, step = 1, readOnly, ...props }, + ref, + ) => { + const [localValue, setLocalValue] = React.useState(value?.toString() ?? ''); + const initialValue = React.useRef(value?.toString() ?? ''); - // Create a debounced version of onChange - const debouncedOnChange = React.useMemo( - () => debounce((value: number) => onChange(value), 500), - [onChange], - ); + // Create a debounced version of onChange + const debouncedOnChange = React.useMemo( + () => debounce((value: number) => onChange(value), 500), + [onChange], + ); - // Update localValue when value prop changes - React.useEffect(() => { - setLocalValue(value?.toString() ?? ''); - initialValue.current = value?.toString() ?? ''; - }, [value]); + // Update localValue when value prop changes + React.useEffect(() => { + setLocalValue(value?.toString() ?? ''); + initialValue.current = value?.toString() ?? ''; + }, [value]); - // Cleanup debounce on unmount - React.useEffect(() => { - return () => { - debouncedOnChange.cancel(); - }; - }, [debouncedOnChange]); + // Cleanup debounce on unmount + React.useEffect(() => { + return () => { + debouncedOnChange.cancel(); + }; + }, [debouncedOnChange]); - const handleBlur = () => { - const newValue = parseFloat(localValue); - if (!isNaN(newValue) && localValue !== initialValue.current) { - debouncedOnChange.cancel(); // Cancel any pending debounced calls - onChange(applyConstraints(newValue)); - } - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Escape') { - setLocalValue(initialValue.current); // Revert to initial value - debouncedOnChange.cancel(); // Cancel any pending debounced calls - } else if (event.key === 'Enter') { + const handleBlur = () => { const newValue = parseFloat(localValue); if (!isNaN(newValue) && localValue !== initialValue.current) { - onChange(applyConstraints(newValue)); // Fire onChange immediately when Enter is pressed debouncedOnChange.cancel(); // Cancel any pending debounced calls + onChange(applyConstraints(newValue)); } - } - }; + }; - const handleChange = (event: React.ChangeEvent) => { - const newValue = event.target.value; - setLocalValue(newValue); - const parsedValue = parseFloat(newValue); - if (!isNaN(parsedValue)) { - debouncedOnChange(applyConstraints(parsedValue)); // Trigger debounced onChange - } - }; + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + setLocalValue(initialValue.current); // Revert to initial value + debouncedOnChange.cancel(); // Cancel any pending debounced calls + } else if (event.key === 'Enter') { + const newValue = parseFloat(localValue); + if (!isNaN(newValue) && localValue !== initialValue.current) { + onChange(applyConstraints(newValue)); // Fire onChange immediately when Enter is pressed + debouncedOnChange.cancel(); // Cancel any pending debounced calls + } + } + }; - const applyConstraints = (value: number): number => { - let constrainedValue = value; - if (min !== undefined) { - constrainedValue = Math.max(min, constrainedValue); - } - if (max !== undefined) { - constrainedValue = Math.min(max, constrainedValue); - } - return Math.round(constrainedValue / step) * step; - }; + const handleChange = (event: React.ChangeEvent) => { + const newValue = event.target.value; + setLocalValue(newValue); + const parsedValue = parseFloat(newValue); + if (!isNaN(parsedValue)) { + debouncedOnChange(applyConstraints(parsedValue)); // Trigger debounced onChange + } + }; - return ( - - ); -}); + const applyConstraints = (value: number): number => { + let constrainedValue = value; + if (min !== undefined) { + constrainedValue = Math.max(min, constrainedValue); + } + if (max !== undefined) { + constrainedValue = Math.min(max, constrainedValue); + } + return Math.round(constrainedValue / step) * step; + }; + + return ( + + ); + }, +); SmartNumberInput.displayName = 'SmartNumberInput'; diff --git a/desktop/src/components/ui/smart-text-input.tsx b/desktop/src/components/ui/smart-text-input.tsx index 291a0e4f..abec3f2b 100644 --- a/desktop/src/components/ui/smart-text-input.tsx +++ b/desktop/src/components/ui/smart-text-input.tsx @@ -6,10 +6,11 @@ interface SmartTextInputProps { value: string | null; onChange: (newValue: string) => void; className?: string; + readOnly?: boolean; } const SmartTextInput = React.forwardRef( - ({ value, onChange, className, ...props }, ref) => { + ({ value, onChange, className, readOnly, ...props }, ref) => { const [localValue, setLocalValue] = React.useState(value ?? ''); const initialValue = React.useRef(value ?? ''); @@ -69,6 +70,7 @@ const SmartTextInput = React.forwardRef( onChange={handleChange} onBlur={handleBlur} onKeyDown={handleKeyDown} + readOnly={readOnly} {...props} /> ); diff --git a/desktop/src/mutations/use-update-boolean-field-value-mutation.tsx b/desktop/src/mutations/use-update-boolean-field-value-mutation.tsx deleted file mode 100644 index 66143ba4..00000000 --- a/desktop/src/mutations/use-update-boolean-field-value-mutation.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateBooleanFieldValueMutationProps { - recordId: string; - fieldId: string; - value: boolean; -} - -export const useUpdateBooleanFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (!value) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: fieldId, - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - number_value: 1, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => ob.doNothing()) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-update-email-field-value-mutation.tsx b/desktop/src/mutations/use-update-email-field-value-mutation.tsx deleted file mode 100644 index 66db5c48..00000000 --- a/desktop/src/mutations/use-update-email-field-value-mutation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateEmailFieldValueMutationProps { - recordId: string; - fieldId: string; - value: string; -} - -export const useUpdateEmailFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (value.length === 0) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: '1', - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - text_value: value, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - text_value: value, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-update-multi-select-field-value-mutation.tsx b/desktop/src/mutations/use-update-multi-select-field-value-mutation.tsx deleted file mode 100644 index 03b5c626..00000000 --- a/desktop/src/mutations/use-update-multi-select-field-value-mutation.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateMultiSelectFieldValueMutationProps { - recordId: string; - fieldId: string; - selectOptionId: string; - add: boolean; -} - -export const useUpdateMultiSelectFieldValueMutation = () => { - const workspace = useWorkspace(); - - return useMutation({ - mutationFn: async ({ - recordId, - fieldId, - selectOptionId, - add, - }: UpdateMultiSelectFieldValueMutationProps) => { - if (add) { - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: selectOptionId, - foreign_node_id: selectOptionId, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => ob.doNothing()) - .compile(); - - await workspace.mutate(query); - } else { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and([ - eb('node_id', '=', recordId), - eb('type', '=', fieldId), - eb('key', '=', selectOptionId), - ]), - ) - .compile(); - - await workspace.mutate(query); - } - }, - }); -}; diff --git a/desktop/src/mutations/use-update-number-field-value-mutation.tsx b/desktop/src/mutations/use-update-number-field-value-mutation.tsx deleted file mode 100644 index a9f429e3..00000000 --- a/desktop/src/mutations/use-update-number-field-value-mutation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateNumberFieldValueMutationProps { - recordId: string; - fieldId: string; - value: number | null; -} - -export const useUpdateNumberFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (value === null) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: '1', - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - number_value: value, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - number_value: value, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-update-phone-field-value-mutation.tsx b/desktop/src/mutations/use-update-phone-field-value-mutation.tsx deleted file mode 100644 index adbfa3ae..00000000 --- a/desktop/src/mutations/use-update-phone-field-value-mutation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdatePhoneFieldValueMutationProps { - recordId: string; - fieldId: string; - value: string; -} - -export const useUpdatePhoneFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (value.length === 0) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: '1', - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - text_value: value, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - text_value: value, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-update-select-field-value-mutation.tsx b/desktop/src/mutations/use-update-select-field-value-mutation.tsx deleted file mode 100644 index e5bb939a..00000000 --- a/desktop/src/mutations/use-update-select-field-value-mutation.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateSelectFieldValueMutationProps { - recordId: string; - fieldId: string; - selectOptionId: string; - add: boolean; -} - -export const useUpdateSelectFieldValueMutation = () => { - const workspace = useWorkspace(); - - return useMutation({ - mutationFn: async ({ - recordId, - fieldId, - selectOptionId, - add, - }: UpdateSelectFieldValueMutationProps) => { - if (add) { - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - foreign_node_id: selectOptionId, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - foreign_node_id: selectOptionId, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - } else { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and([ - eb('node_id', '=', recordId), - eb('type', '=', fieldId), - eb('key', '=', '1'), - ]), - ) - .compile(); - - await workspace.mutate(query); - } - }, - }); -}; diff --git a/desktop/src/mutations/use-update-text-field-value-mutation.tsx b/desktop/src/mutations/use-update-text-field-value-mutation.tsx deleted file mode 100644 index 773b28cf..00000000 --- a/desktop/src/mutations/use-update-text-field-value-mutation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateTextFieldValueMutationProps { - recordId: string; - fieldId: string; - value: string; -} - -export const useUpdateTextFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (value.length === 0) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: fieldId, - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - text_value: value, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - text_value: value, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-update-url-field-value-mutation.tsx b/desktop/src/mutations/use-update-url-field-value-mutation.tsx deleted file mode 100644 index dfd331ce..00000000 --- a/desktop/src/mutations/use-update-url-field-value-mutation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface UpdateUrlFieldValueMutationProps { - recordId: string; - fieldId: string; - value: string; -} - -export const useUpdateUrlFieldValueMutation = () => { - const workspace = useWorkspace(); - return useMutation({ - mutationFn: async ({ recordId, fieldId, value }) => { - if (value.length === 0) { - const query = workspace.schema - .deleteFrom('node_attributes') - .where((eb) => - eb.and({ - node_id: recordId, - type: fieldId, - key: fieldId, - }), - ) - .compile(); - - await workspace.mutate(query); - return; - } - - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: recordId, - type: fieldId, - key: '1', - text_value: value, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((ob) => - ob.doUpdateSet({ - text_value: value, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -}; diff --git a/desktop/src/mutations/use-view-filter-operator-update-mutation.tsx b/desktop/src/mutations/use-view-filter-operator-update-mutation.tsx deleted file mode 100644 index 21cbfe80..00000000 --- a/desktop/src/mutations/use-view-filter-operator-update-mutation.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useWorkspace } from '@/contexts/workspace'; -import { AttributeTypes } from '@/lib/constants'; -import { NeuronId } from '@/lib/id'; -import { useMutation } from '@tanstack/react-query'; - -interface ViewFilterOperatorUpdateMutationInput { - filterId: string; - operator: string; -} - -export const useViewFilterOperatorUpdateMutation = () => { - const workspace = useWorkspace(); - - return useMutation({ - mutationFn: async ({ - filterId, - operator, - }: ViewFilterOperatorUpdateMutationInput) => { - const query = workspace.schema - .insertInto('node_attributes') - .values({ - node_id: filterId, - type: AttributeTypes.Operator, - key: '1', - text_value: operator, - created_at: new Date().toISOString(), - created_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }) - .onConflict((b) => - b.doUpdateSet({ - text_value: operator, - updated_at: new Date().toISOString(), - updated_by: workspace.userId, - version_id: NeuronId.generate(NeuronId.Type.Version), - }), - ) - .compile(); - - await workspace.mutate(query); - }, - }); -};