Vlad Chikalkin 3589ab974a
Refactor/components folder structure (#24)
* refactor components/navigation

* refactor components/orders

* refactor components/profile

* refactor components/schedule

* remove components/common/spinner
2025-05-23 17:35:13 +03:00

97 lines
2.5 KiB
TypeScript

/* eslint-disable promise/prefer-await-to-then */
'use client';
import { type CustomerInput } from '@repo/graphql/types';
import { Input } from '@repo/ui/components/ui/input';
import { Label } from '@repo/ui/components/ui/label';
import { type ChangeEvent, useEffect, useRef, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
type ProfileFieldProps = {
readonly disabled?: boolean;
readonly fieldName?: keyof CustomerInput;
readonly id: string;
readonly label: string;
readonly onChange?: (value: CustomerInput) => Promise<void> | void;
readonly readOnly?: boolean;
readonly value: string;
};
export function DataField({
disabled = false,
fieldName,
id,
label,
onChange,
readOnly,
value: initialValue,
}: ProfileFieldProps) {
const [value, setValue] = useState(initialValue);
const { debouncedCallback, isPending } = useDebouncedOnChangeCallback(onChange, fieldName);
const inputRef = useFocus(isPending);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
debouncedCallback(newValue);
};
return (
<div className="space-y-2">
<Label htmlFor={id}>{label}</Label>
<Input
className="bg-secondary outline-none focus:ring-0 focus:ring-offset-0"
disabled={disabled || isPending}
id={id}
onChange={handleChange}
readOnly={readOnly}
ref={inputRef}
value={value}
/>
</div>
);
}
function useDebouncedOnChangeCallback(
callback: ((value: CustomerInput) => Promise<void> | void) | undefined,
fieldName: string | undefined,
) {
const [isPending, setIsPending] = useState(false);
const debouncedCallback = useDebouncedCallback((newValue: string) => {
if (!callback || !fieldName) return;
setIsPending(true);
const result = callback({ [fieldName]: newValue });
if (result instanceof Promise) {
result.finally(() => setIsPending(false));
} else {
setIsPending(false);
}
}, 300);
return {
debouncedCallback,
isPending,
};
}
function useFocus(isPending: boolean) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [isInitialRender, setIsInitialRender] = useState(true);
useEffect(() => {
if (isInitialRender) {
setIsInitialRender(false);
return;
}
if (inputRef.current && isPending) {
inputRef.current.focus();
}
}, [isInitialRender, isPending]);
return inputRef;
}