bugfix: selecting past dates is now possible in appointment and treatment features. bur, add, edit and delete actions are disabled for past dates.

This commit is contained in:
2026-05-18 12:50:25 +03:30
parent eb636db653
commit 3b12c52fd3
4 changed files with 50 additions and 90 deletions

View File

@@ -2,17 +2,11 @@
import { useEffect, useId, useRef, useState } from 'react';
import { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
import {
addCalendarDays,
compareLocalDayStart,
startOfLocalDay,
} from '@/lib/appointmentTime';
import { addCalendarDays, startOfLocalDay } from '@/lib/appointmentTime';
interface ScheduleDayPickerProps {
value: Date;
onChange: (day: Date) => void;
/** Optional lower bound for day selection and previous-day navigation. */
minDate?: Date;
label?: string;
}
@@ -39,27 +33,10 @@ function buildLocalDay(year: number, month: number, day: number): Date {
return new Date(year, month, day, 0, 0, 0, 0);
}
function clampToValidDay(
year: number,
month: number,
day: number,
min?: Date,
): Date {
const maxDay = daysInMonth(year, month);
let next = buildLocalDay(year, month, Math.min(Math.max(1, day), maxDay));
if (min) {
const floor = startOfLocalDay(min);
if (compareLocalDayStart(next, floor) < 0) {
next = floor;
}
}
return next;
}
function yearRange(min?: Date, anchor?: Date): number[] {
const now = new Date();
const startYear = min ? min.getFullYear() : now.getFullYear() - 5;
const endYear = Math.max(now.getFullYear() + 2, anchor?.getFullYear() ?? now.getFullYear());
function yearRange(anchor: Date): number[] {
const anchorYear = anchor.getFullYear();
const startYear = anchorYear - 10;
const endYear = anchorYear + 2;
const years: number[] = [];
for (let y = startYear; y <= endYear; y += 1) {
years.push(y);
@@ -72,21 +49,18 @@ const selectClassName = `
bg-background-card/90 text-text-primary text-sm
pl-2 pr-7 py-1.5
focus:outline-none focus:ring-2 focus:ring-primary/35 focus:border-border-strong
disabled:opacity-50 disabled:cursor-not-allowed
`;
export function ScheduleDayPicker({
value,
onChange,
minDate,
label = 'Schedule date',
}: ScheduleDayPickerProps) {
/**
* Calendar day navigator (arrows + year/month/day panel).
* Does not restrict past dates — parent pages enforce read-only vs editable for schedule grids/forms.
*/
export function ScheduleDayPicker({ value, onChange, label = 'Schedule date' }: ScheduleDayPickerProps) {
const panelId = useId();
const rootRef = useRef<HTMLDivElement>(null);
const [panelOpen, setPanelOpen] = useState(false);
const normalizedValue = startOfLocalDay(value);
const normalizedMin = minDate ? startOfLocalDay(minDate) : undefined;
const labelText = normalizedValue.toLocaleDateString(undefined, {
weekday: 'short',
@@ -95,28 +69,20 @@ export function ScheduleDayPicker({
year: 'numeric',
});
const previousDay = addCalendarDays(normalizedValue, -1);
const canGoPrevious =
!normalizedMin || compareLocalDayStart(previousDay, normalizedMin) >= 0;
const years = yearRange(normalizedMin, normalizedValue);
const years = yearRange(normalizedValue);
const selectedYear = normalizedValue.getFullYear();
const selectedMonth = normalizedValue.getMonth();
const selectedDay = normalizedValue.getDate();
const dayCount = daysInMonth(selectedYear, selectedMonth);
function applyParts(year: number, month: number, day: number, closePanel = false) {
onChange(clampToValidDay(year, month, day, normalizedMin));
const maxDay = daysInMonth(year, month);
onChange(buildLocalDay(year, month, Math.min(Math.max(1, day), maxDay)));
if (closePanel) {
setPanelOpen(false);
}
}
function handlePreviousDay() {
if (!canGoPrevious) return;
onChange(previousDay);
}
useEffect(() => {
if (!panelOpen) return;
@@ -146,9 +112,8 @@ export function ScheduleDayPicker({
<div className="flex items-center gap-1 rounded-[var(--radius-md)] border border-border bg-background-secondary/90 px-1 py-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.02)]">
<button
type="button"
onClick={handlePreviousDay}
disabled={!canGoPrevious}
className="shrink-0 rounded-[var(--radius-sm)] p-2 text-text-muted hover:text-text-primary hover:bg-background-card/80 focus:outline-none focus:ring-2 focus:ring-primary/35 disabled:opacity-40 disabled:pointer-events-none"
onClick={() => onChange(addCalendarDays(normalizedValue, -1))}
className="shrink-0 rounded-[var(--radius-sm)] p-2 text-text-muted hover:text-text-primary hover:bg-background-card/80 focus:outline-none focus:ring-2 focus:ring-primary/35"
aria-label="Previous day"
>
<ChevronLeft className="h-4 w-4 icon-flat" />
@@ -232,17 +197,11 @@ export function ScheduleDayPicker({
}
className={selectClassName}
>
{MONTH_LABELS.map((name, index) => {
const disabled =
normalizedMin &&
selectedYear === normalizedMin.getFullYear() &&
index < normalizedMin.getMonth();
return (
<option key={name} value={index} disabled={disabled}>
{name}
</option>
);
})}
{MONTH_LABELS.map((name, index) => (
<option key={name} value={index}>
{name}
</option>
))}
</select>
<ChevronDown
className="pointer-events-none absolute right-1.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-muted icon-flat"
@@ -272,18 +231,11 @@ export function ScheduleDayPicker({
}
className={selectClassName}
>
{Array.from({ length: dayCount }, (_, i) => i + 1).map((day) => {
const disabled =
normalizedMin &&
selectedYear === normalizedMin.getFullYear() &&
selectedMonth === normalizedMin.getMonth() &&
day < normalizedMin.getDate();
return (
<option key={day} value={day} disabled={disabled}>
{day}
</option>
);
})}
{Array.from({ length: dayCount }, (_, i) => i + 1).map((day) => (
<option key={day} value={day}>
{day}
</option>
))}
</select>
<ChevronDown
className="pointer-events-none absolute right-1.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-muted icon-flat"