Refactor table view cells

This commit is contained in:
Hakan Shehu
2024-09-13 01:06:43 +02:00
parent 1b3ccc796c
commit 8c59430d86
19 changed files with 310 additions and 755 deletions

View File

@@ -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<boolean>(
@@ -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',
});
}
}
}}
/>

View File

@@ -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<string>(
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 (
<input
value={text}
<SmartTextInput
value={getEmailValue(record, field)}
readOnly={!canEdit || isPending}
onChange={(e) => 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"

View File

@@ -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,
});
}
}}

View File

@@ -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<string>(
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 (
<input
className="flex h-full w-full cursor-pointer flex-row items-center gap-1 p-1 text-sm focus-visible:cursor-text"
<SmartNumberInput
value={getNumberValue(record, field)}
readOnly={!canEdit || isPending}
value={input ?? ''}
onChange={(e) => 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"
/>
);
};

View File

@@ -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<string>(
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 (
<input
value={text}
<SmartTextInput
value={getPhoneValue(record, field)}
readOnly={!canEdit || isPending}
onChange={(e) => 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"

View File

@@ -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);
}}
/>
</PopoverContent>

View File

@@ -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<string>(
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 (
<input
<SmartTextInput
value={getTextValue(record, field)}
readOnly={!canEdit || isPending}
value={text}
onChange={(e) => 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"

View File

@@ -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<string>(
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 (
<HoverCard openDelay={300}>
<HoverCardTrigger>
<input
<SmartTextInput
value={text}
readOnly={!canEdit || isPending}
onChange={(e) => 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"
/>
</HoverCardTrigger>
<HoverCardContent

View File

@@ -9,94 +9,101 @@ interface SmartNumberInputProps {
min?: number;
max?: number;
step?: number;
readOnly?: boolean;
}
const SmartNumberInput = React.forwardRef<
HTMLInputElement,
SmartNumberInputProps
>(({ 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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
const newValue = event.target.value;
setLocalValue(newValue);
const parsedValue = parseFloat(newValue);
if (!isNaN(parsedValue)) {
debouncedOnChange(applyConstraints(parsedValue)); // Trigger debounced onChange
}
};
return (
<input
type="number"
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
value={localValue}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
step={step}
min={min}
max={max}
{...props}
/>
);
});
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 (
<input
type="number"
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
value={localValue}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
step={step}
min={min}
max={max}
readOnly={readOnly}
{...props}
/>
);
},
);
SmartNumberInput.displayName = 'SmartNumberInput';

View File

@@ -6,10 +6,11 @@ interface SmartTextInputProps {
value: string | null;
onChange: (newValue: string) => void;
className?: string;
readOnly?: boolean;
}
const SmartTextInput = React.forwardRef<HTMLInputElement, SmartTextInputProps>(
({ 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<HTMLInputElement, SmartTextInputProps>(
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
readOnly={readOnly}
{...props}
/>
);

View File

@@ -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<void, Error, UpdateBooleanFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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<void, Error, UpdateEmailFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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);
}
},
});
};

View File

@@ -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<void, Error, UpdateNumberFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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<void, Error, UpdatePhoneFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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);
}
},
});
};

View File

@@ -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<void, Error, UpdateTextFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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<void, Error, UpdateUrlFieldValueMutationProps>({
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);
},
});
};

View File

@@ -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);
},
});
};