feat(profile): implement local hooks for profile and service data editing
- Added `useProfileEdit` and `useServiceEdit` hooks to manage pending changes and save functionality for profile and service data cards. - Updated `ProfileDataCard` and `ServiceDataCard` components to utilize these hooks, enhancing user experience with save and cancel options. - Introduced buttons for saving and canceling changes, improving the overall interactivity of the forms. - Refactored input handling to use `updateField` for better state management.
This commit is contained in:
parent
2e849857f2
commit
a220d0a369
@ -7,6 +7,7 @@ import { useCustomerMutation, useCustomerQuery } from '@/hooks/api/customers';
|
||||
import { Button } from '@repo/ui/components/ui/button';
|
||||
import { Card } from '@repo/ui/components/ui/card';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function ContactDataCard({ telegramId }: Readonly<ProfileProps>) {
|
||||
const { data: { customer } = {} } = useCustomerQuery({ telegramId });
|
||||
@ -31,7 +32,8 @@ export function ContactDataCard({ telegramId }: Readonly<ProfileProps>) {
|
||||
|
||||
export function ProfileDataCard() {
|
||||
const { data: { customer } = {} } = useCustomerQuery();
|
||||
const { mutate: updateCustomer } = useCustomerMutation();
|
||||
const { cancelChanges, hasChanges, isPending, resetTrigger, saveChanges, updateField } =
|
||||
useProfileEdit();
|
||||
|
||||
if (!customer) return null;
|
||||
|
||||
@ -41,8 +43,9 @@ export function ProfileDataCard() {
|
||||
<CardSectionHeader title="Ваши данные" />
|
||||
<TextField
|
||||
id="name"
|
||||
key={`name-${resetTrigger}`}
|
||||
label="Имя"
|
||||
onChange={(value) => updateCustomer({ data: { name: value } })}
|
||||
onChange={(value) => updateField('name', value)}
|
||||
value={customer?.name ?? ''}
|
||||
/>
|
||||
<TextField disabled id="phone" label="Телефон" readOnly value={customer?.phone ?? ''} />
|
||||
@ -50,13 +53,54 @@ export function ProfileDataCard() {
|
||||
checked={customer.role !== 'client'}
|
||||
description="Разрешить другим пользователям записываться к вам"
|
||||
onChange={(checked) =>
|
||||
updateCustomer({
|
||||
data: { role: checked ? Role.Master : Role.Client },
|
||||
})
|
||||
updateField('role', checked ? Role.Master : Role.Client)
|
||||
}
|
||||
text="Быть мастером"
|
||||
/> */}
|
||||
{hasChanges && (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button disabled={isPending} onClick={cancelChanges} variant="outline">
|
||||
Отмена
|
||||
</Button>
|
||||
<Button disabled={isPending} onClick={saveChanges}>
|
||||
{isPending ? 'Сохранение...' : 'Сохранить'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function useProfileEdit() {
|
||||
const { isPending, mutate } = useCustomerMutation();
|
||||
const [pendingChanges, setPendingChanges] = useState<Record<string, unknown>>({});
|
||||
const [resetTrigger, setResetTrigger] = useState(0);
|
||||
|
||||
const updateField = (field: string, value: unknown) => {
|
||||
setPendingChanges((previous) => ({ ...previous, [field]: value }));
|
||||
};
|
||||
|
||||
const saveChanges = () => {
|
||||
if (Object.keys(pendingChanges).length === 0) return;
|
||||
|
||||
mutate({ data: pendingChanges });
|
||||
setPendingChanges({});
|
||||
};
|
||||
|
||||
const cancelChanges = () => {
|
||||
setPendingChanges({});
|
||||
setResetTrigger((previous) => previous + 1);
|
||||
};
|
||||
|
||||
const hasChanges = Object.keys(pendingChanges).length > 0;
|
||||
|
||||
return {
|
||||
cancelChanges,
|
||||
hasChanges,
|
||||
isPending,
|
||||
resetTrigger,
|
||||
saveChanges,
|
||||
updateField,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
import { NumberField, TextareaField, TextField, TimeField } from '@/components/shared/data-fields';
|
||||
import { useServiceMutation, useServiceQuery } from '@/hooks/api/services';
|
||||
import { Button } from '@repo/ui/components/ui/button';
|
||||
import { Card } from '@repo/ui/components/ui/card';
|
||||
import { convertTimeString } from '@repo/utils/datetime-format';
|
||||
import { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
serviceId: string;
|
||||
@ -11,7 +13,8 @@ type Props = {
|
||||
|
||||
export function ServiceDataCard({ serviceId }: Readonly<Props>) {
|
||||
const { data: { service } = {} } = useServiceQuery({ documentId: serviceId });
|
||||
const { mutate } = useServiceMutation({ documentId: serviceId });
|
||||
const { cancelChanges, hasChanges, isPending, resetTrigger, saveChanges, updateField } =
|
||||
useServiceEdit(serviceId);
|
||||
|
||||
if (!service) return null;
|
||||
|
||||
@ -20,37 +23,78 @@ export function ServiceDataCard({ serviceId }: Readonly<Props>) {
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextField
|
||||
id="name"
|
||||
key={`name-${resetTrigger}`}
|
||||
label="Название"
|
||||
onChange={(name) => mutate({ data: { name } })}
|
||||
onChange={(name) => updateField('name', name)}
|
||||
value={service?.name ?? ''}
|
||||
/>
|
||||
<TimeField
|
||||
id="duration"
|
||||
key={`duration-${resetTrigger}`}
|
||||
label="Длительность"
|
||||
onChange={(time) =>
|
||||
mutate({
|
||||
data: {
|
||||
duration: convertTimeString(time).db(),
|
||||
},
|
||||
})
|
||||
}
|
||||
onChange={(time) => updateField('duration', convertTimeString(time).db())}
|
||||
value={service?.duration ?? ''}
|
||||
/>
|
||||
<NumberField
|
||||
id="price"
|
||||
key={`price-${resetTrigger}`}
|
||||
label="Цена (₽)"
|
||||
min={0}
|
||||
onChange={(price) => mutate({ data: { price } })}
|
||||
onChange={(price) => updateField('price', price)}
|
||||
value={service?.price ?? null}
|
||||
/>
|
||||
<TextareaField
|
||||
id="description"
|
||||
key={`description-${resetTrigger}`}
|
||||
label="Описание"
|
||||
onChange={(description) => mutate({ data: { description } })}
|
||||
onChange={(description) => updateField('description', description)}
|
||||
rows={4}
|
||||
value={service?.description ?? ''}
|
||||
/>
|
||||
{hasChanges && (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button disabled={isPending} onClick={cancelChanges} variant="outline">
|
||||
Отмена
|
||||
</Button>
|
||||
<Button disabled={isPending} onClick={saveChanges}>
|
||||
{isPending ? 'Сохранение...' : 'Сохранить'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function useServiceEdit(serviceId: string) {
|
||||
const { isPending, mutate } = useServiceMutation({ documentId: serviceId });
|
||||
const [pendingChanges, setPendingChanges] = useState<Record<string, unknown>>({});
|
||||
const [resetTrigger, setResetTrigger] = useState(0);
|
||||
|
||||
const updateField = (field: string, value: unknown) => {
|
||||
setPendingChanges((previous) => ({ ...previous, [field]: value }));
|
||||
};
|
||||
|
||||
const saveChanges = () => {
|
||||
if (Object.keys(pendingChanges).length === 0) return;
|
||||
|
||||
mutate({ data: pendingChanges });
|
||||
setPendingChanges({});
|
||||
};
|
||||
|
||||
const cancelChanges = () => {
|
||||
setPendingChanges({});
|
||||
setResetTrigger((previous) => previous + 1);
|
||||
};
|
||||
|
||||
const hasChanges = Object.keys(pendingChanges).length > 0;
|
||||
|
||||
return {
|
||||
cancelChanges,
|
||||
hasChanges,
|
||||
isPending,
|
||||
resetTrigger,
|
||||
saveChanges,
|
||||
updateField,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { Button } from '@repo/ui/components/ui/button';
|
||||
import { Card } from '@repo/ui/components/ui/card';
|
||||
import { Ban, Check, Lock, RotateCcw, Trash2, Undo, Unlock } from 'lucide-react';
|
||||
import { Ban, Check, Lock, RotateCcw, Save, Trash2, Undo, Unlock } from 'lucide-react';
|
||||
|
||||
type FloatingActionPanelProps = {
|
||||
readonly isLoading?: boolean;
|
||||
@ -13,6 +13,7 @@ type FloatingActionPanelProps = {
|
||||
readonly onDelete?: () => void;
|
||||
readonly onRepeat?: () => void;
|
||||
readonly onReturn?: () => void;
|
||||
readonly onSave?: () => void;
|
||||
readonly onToggle?: () => void;
|
||||
};
|
||||
|
||||
@ -25,10 +26,20 @@ export default function FloatingActionPanel({
|
||||
onDelete,
|
||||
onRepeat,
|
||||
onReturn,
|
||||
onSave,
|
||||
onToggle,
|
||||
}: FloatingActionPanelProps) {
|
||||
// Если не переданы обработчики, скрываем панель
|
||||
if (!onCancel && !onConfirm && !onDelete && !onComplete && !onRepeat && !onToggle && !onReturn)
|
||||
if (
|
||||
!onCancel &&
|
||||
!onConfirm &&
|
||||
!onDelete &&
|
||||
!onComplete &&
|
||||
!onRepeat &&
|
||||
!onToggle &&
|
||||
!onReturn &&
|
||||
!onSave
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
@ -140,6 +151,19 @@ export default function FloatingActionPanel({
|
||||
<span>Вернуть</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Кнопка сохранить */}
|
||||
{onSave && (
|
||||
<Button
|
||||
className="w-full rounded-2xl bg-green-600 text-sm text-white shadow-lg transition-all duration-200 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600 sm:w-auto"
|
||||
disabled={isLoading}
|
||||
onClick={onSave}
|
||||
size="sm"
|
||||
>
|
||||
<Save className="mr-2 size-4" />
|
||||
<span>Сохранить</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -10,7 +10,7 @@ type FieldProps = {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly min?: number;
|
||||
readonly onChange?: (value: null | number) => Promise<void> | void;
|
||||
readonly onChange?: (value: null | number) => void;
|
||||
readonly readOnly?: boolean;
|
||||
readonly value: null | number;
|
||||
};
|
||||
|
||||
@ -9,7 +9,7 @@ type FieldProps = {
|
||||
readonly disabled?: boolean;
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly onChange?: (value: string) => Promise<void> | void;
|
||||
readonly onChange?: (value: string) => void;
|
||||
readonly readOnly?: boolean;
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
@ -8,7 +8,7 @@ type FieldProps = {
|
||||
readonly disabled?: boolean;
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly onChange?: (value: string) => Promise<void> | void;
|
||||
readonly onChange?: (value: string) => void;
|
||||
readonly readOnly?: boolean;
|
||||
readonly rows?: number;
|
||||
readonly value: string;
|
||||
|
||||
@ -9,7 +9,7 @@ type FieldProps = {
|
||||
readonly disabled?: boolean;
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly onChange?: (value: string) => Promise<void> | void;
|
||||
readonly onChange?: (value: string) => void;
|
||||
readonly readOnly?: boolean;
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user