Files
dyolink/frontend/src/components/staff/WorkingHoursEditor.tsx

172 lines
6.0 KiB
TypeScript

'use client';
import { Plus, Trash2 } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/shared/Button';
import { Checkbox } from '@/components/ui/shared/Checkbox';
import {
MINUTES_PER_DAY,
WEEKDAY_KEYS,
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) {
const t = useTranslations('staff.workingHours');
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">
<p className="text-sm text-text-secondary">{t('intro')}</p>
<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">
{t(WEEKDAY_KEYS[day.dayOfWeek])}
</span>
<Checkbox
checked={day.isWorking}
disabled={disabled}
label={t('workingDay')}
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">
<label className="block text-xs text-text-muted mb-1">{t('start')}</label>
<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">
<label className="block text-xs text-text-muted mb-1">{t('end')}</label>
<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"
aria-label={t('removeShift')}
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" />
{t('addShift')}
</Button>
</div>
)}
</div>
))}
</div>
<Checkbox
checked={autoRepeatWeekly}
disabled={disabled}
label={t('autoRepeatWeekly')}
onChange={onAutoRepeatWeeklyChange}
/>
</div>
);
}