2026-06-10 19:44:09 +03:30
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { Plus, Trash2 } from 'lucide-react';
|
2026-06-20 14:51:43 +03:30
|
|
|
import { useTranslations } from 'next-intl';
|
2026-06-10 19:44:09 +03:30
|
|
|
import { Button } from '@/components/ui/shared/Button';
|
|
|
|
|
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
|
|
|
|
import {
|
|
|
|
|
MINUTES_PER_DAY,
|
2026-06-20 14:51:43 +03:30
|
|
|
WEEKDAY_KEYS,
|
2026-06-10 19:44:09 +03:30
|
|
|
minutesToTimeInput,
|
|
|
|
|
timeInputToMinutes,
|
|
|
|
|
type WorkingHoursEditorDay,
|
|
|
|
|
} from '@/components/staff/workingHours';
|
|
|
|
|
|
|
|
|
|
interface WorkingHoursEditorProps {
|
|
|
|
|
days: WorkingHoursEditorDay[];
|
|
|
|
|
autoRepeatWeekly: boolean;
|
|
|
|
|
onDaysChange: (days: WorkingHoursEditorDay[]) => void;
|
|
|
|
|
onAutoRepeatWeeklyChange: (value: boolean) => void;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const timeInputClass =
|
|
|
|
|
'w-full rounded-[var(--radius-md)] border border-border bg-background-secondary/90 text-text-primary px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary/35';
|
|
|
|
|
|
|
|
|
|
export function WorkingHoursEditor({
|
|
|
|
|
days,
|
|
|
|
|
autoRepeatWeekly,
|
|
|
|
|
onDaysChange,
|
|
|
|
|
onAutoRepeatWeeklyChange,
|
|
|
|
|
disabled = false,
|
|
|
|
|
}: WorkingHoursEditorProps) {
|
2026-06-20 14:51:43 +03:30
|
|
|
const t = useTranslations('staff.workingHours');
|
|
|
|
|
|
2026-06-10 19:44:09 +03:30
|
|
|
function updateDay(dayOfWeek: number, patch: Partial<WorkingHoursEditorDay>) {
|
|
|
|
|
onDaysChange(
|
|
|
|
|
days.map((day) => (day.dayOfWeek === dayOfWeek ? { ...day, ...patch } : day)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateShift(
|
|
|
|
|
dayOfWeek: number,
|
|
|
|
|
shiftIndex: number,
|
|
|
|
|
field: 'startMinute' | 'endMinute',
|
|
|
|
|
value: string,
|
|
|
|
|
) {
|
|
|
|
|
const minutes = timeInputToMinutes(value);
|
|
|
|
|
if (minutes == null) return;
|
|
|
|
|
const day = days.find((d) => d.dayOfWeek === dayOfWeek);
|
|
|
|
|
if (!day) return;
|
|
|
|
|
const shifts = day.shifts.map((shift, index) =>
|
|
|
|
|
index === shiftIndex ? { ...shift, [field]: minutes } : shift,
|
|
|
|
|
);
|
|
|
|
|
updateDay(dayOfWeek, { shifts });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addShift(dayOfWeek: number) {
|
|
|
|
|
const day = days.find((d) => d.dayOfWeek === dayOfWeek);
|
|
|
|
|
if (!day) return;
|
|
|
|
|
const last = day.shifts[day.shifts.length - 1];
|
|
|
|
|
const startMinute = last ? Math.min(last.endMinute + 60, MINUTES_PER_DAY - 60) : 9 * 60;
|
|
|
|
|
updateDay(dayOfWeek, {
|
|
|
|
|
shifts: [...day.shifts, { startMinute, endMinute: Math.min(startMinute + 120, MINUTES_PER_DAY) }],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeShift(dayOfWeek: number, shiftIndex: number) {
|
|
|
|
|
const day = days.find((d) => d.dayOfWeek === dayOfWeek);
|
|
|
|
|
if (!day || day.shifts.length <= 1) return;
|
|
|
|
|
updateDay(dayOfWeek, {
|
|
|
|
|
shifts: day.shifts.filter((_, index) => index !== shiftIndex),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-4">
|
2026-06-20 14:51:43 +03:30
|
|
|
<p className="text-sm text-text-secondary">{t('intro')}</p>
|
2026-06-10 19:44:09 +03:30
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{days.map((day) => (
|
|
|
|
|
<div
|
|
|
|
|
key={day.dayOfWeek}
|
|
|
|
|
className="rounded-[var(--radius-md)] border border-border/60 bg-background-card/40 px-3 py-3 space-y-3"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<span className="text-sm font-medium text-text-primary w-10">
|
2026-06-20 14:51:43 +03:30
|
|
|
{t(WEEKDAY_KEYS[day.dayOfWeek])}
|
2026-06-10 19:44:09 +03:30
|
|
|
</span>
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={day.isWorking}
|
|
|
|
|
disabled={disabled}
|
2026-06-20 14:51:43 +03:30
|
|
|
label={t('workingDay')}
|
2026-06-10 19:44:09 +03:30
|
|
|
onChange={(checked) => {
|
|
|
|
|
updateDay(day.dayOfWeek, {
|
|
|
|
|
isWorking: checked,
|
|
|
|
|
shifts: checked
|
|
|
|
|
? day.shifts.length > 0
|
|
|
|
|
? day.shifts
|
|
|
|
|
: [{ startMinute: 9 * 60, endMinute: 17 * 60 }]
|
|
|
|
|
: day.shifts,
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{day.isWorking && (
|
|
|
|
|
<div className="space-y-2 pl-0 sm:pl-10">
|
|
|
|
|
{day.shifts.map((shift, shiftIndex) => (
|
|
|
|
|
<div key={shiftIndex} className="flex items-end gap-2">
|
|
|
|
|
<div className="flex-1">
|
2026-06-20 14:51:43 +03:30
|
|
|
<label className="block text-xs text-text-muted mb-1">{t('start')}</label>
|
2026-06-10 19:44:09 +03:30
|
|
|
<input
|
|
|
|
|
type="time"
|
|
|
|
|
step={300}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
className={timeInputClass}
|
|
|
|
|
value={minutesToTimeInput(shift.startMinute)}
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
updateShift(day.dayOfWeek, shiftIndex, 'startMinute', e.target.value)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1">
|
2026-06-20 14:51:43 +03:30
|
|
|
<label className="block text-xs text-text-muted mb-1">{t('end')}</label>
|
2026-06-10 19:44:09 +03:30
|
|
|
<input
|
|
|
|
|
type="time"
|
|
|
|
|
step={300}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
className={timeInputClass}
|
|
|
|
|
value={minutesToTimeInput(shift.endMinute)}
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
updateShift(day.dayOfWeek, shiftIndex, 'endMinute', e.target.value)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
disabled={disabled || day.shifts.length <= 1}
|
|
|
|
|
className="p-2 rounded-md text-text-muted hover:text-red-500 hover:bg-red-500/10 disabled:opacity-40 disabled:cursor-not-allowed"
|
2026-06-20 14:51:43 +03:30
|
|
|
aria-label={t('removeShift')}
|
2026-06-10 19:44:09 +03:30
|
|
|
onClick={() => removeShift(day.dayOfWeek, shiftIndex)}
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onClick={() => addShift(day.dayOfWeek)}
|
|
|
|
|
>
|
|
|
|
|
<Plus className="w-3.5 h-3.5 mr-1" />
|
2026-06-20 14:51:43 +03:30
|
|
|
{t('addShift')}
|
2026-06-10 19:44:09 +03:30
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={autoRepeatWeekly}
|
|
|
|
|
disabled={disabled}
|
2026-06-20 14:51:43 +03:30
|
|
|
label={t('autoRepeatWeekly')}
|
2026-06-10 19:44:09 +03:30
|
|
|
onChange={onAutoRepeatWeeklyChange}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|