supersam/src/components/orders/CalendarWidget.jsx

186 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from "react";
import { Button } from "../UI/Button";
const WEEK_DAY_LABELS = ["ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ", "ВС"];
const toDateKey = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const isWeekendDate = (date) => {
const day = date.getDay();
return day === 0 || day === 6;
};
const isSelectableCalendarDate = (date, minDateKey) => {
const dateKey = toDateKey(date);
return dateKey >= minDateKey && !isWeekendDate(date);
};
const formatDateForDisplay = (value) => {
if (!value) {
return "Выберите дату";
}
const [year, month, day] = value.split("-").map(Number);
if (!year || !month || !day) {
return value;
}
return new Date(year, month - 1, day).toLocaleDateString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
};
const CalendarWidget = ({
label,
selectedDate,
onDateChange,
minDateKey,
isCalendarOpen,
setIsCalendarOpen,
currentMonth,
setCurrentMonth,
calendarDays,
monthLabel,
canGoBack,
timeOptions,
selectedTime,
onTimeChange,
layoutClassName,
calendarClassName,
timeClassName,
}) => {
return (
<div className={layoutClassName}>
<div className={calendarClassName}>
<Button
variant="ghost"
aria-label={label}
aria-expanded={isCalendarOpen}
className="flex min-h-[54px] w-full items-center justify-between rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 text-left text-sm font-medium !text-[var(--color-text)] transition hover:border-[var(--color-accent)] focus:border-[var(--color-accent)] focus:outline-none"
onClick={() => setIsCalendarOpen((current) => !current)}
>
<span>{selectedDate ? formatDateForDisplay(selectedDate) : "Выберите дату"}</span>
<span aria-hidden="true" className="text-[var(--color-text-muted)]">▾</span>
</Button>
{isCalendarOpen ? (
<div className="rounded-[20px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-3 shadow-lg absolute left-0 top-full z-50 mt-2 w-[300px]">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
{label}
</p>
<h4
className="mt-1 text-base font-semibold capitalize"
style={{ color: "var(--color-text)" }}
>
{monthLabel}
</h4>
</div>
<div className="flex items-center gap-2">
<button
type="button"
disabled={!canGoBack}
aria-label="Предыдущий месяц"
className="flex h-9 w-9 items-center justify-center rounded-full border border-[var(--color-border)] text-sm text-[var(--color-text-muted)] transition hover:border-[var(--color-accent)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-40"
onClick={() => setCurrentMonth((month) => new Date(month.getFullYear(), month.getMonth() - 1, 1))}
>
‹
</button>
<button
type="button"
aria-label="Следующий месяц"
className="flex h-9 w-9 items-center justify-center rounded-full border border-[var(--color-border)] text-sm text-[var(--color-text-muted)] transition hover:border-[var(--color-accent)] hover:text-[var(--color-text)]"
onClick={() => setCurrentMonth((month) => new Date(month.getFullYear(), month.getMonth() + 1, 1))}
>
›
</button>
</div>
</div>
<div className="mt-4 grid grid-cols-7 gap-1 text-center text-[10px] font-semibold uppercase text-[var(--color-text-muted)]">
{WEEK_DAY_LABELS.map((day) => (
<div key={day} className="px-1 py-1">
{day}
</div>
))}
</div>
<div className="mt-1 grid grid-cols-7 gap-1">
{calendarDays.map((day, index) => {
if (!day) {
return <div key={`empty-${index}`} className="aspect-square" />;
}
const dateKey = toDateKey(day);
const isWeekend = isWeekendDate(day);
const isSelectable = isSelectableCalendarDate(day, minDateKey);
const isSelected = dateKey === selectedDate;
const isDisabled = !isSelectable;
const dayNumber = String(day.getDate()).padStart(2, "0");
return (
<Button
key={dateKey}
variant="ghost"
disabled={isDisabled}
title={isWeekend ? "Выходной, доставки нет" : isSelectable ? "Можно выбрать" : "Недоступно"}
className={[
"relative flex aspect-square items-center justify-center rounded-xl border text-sm font-semibold transition",
isSelected
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)] !text-[var(--color-text)]"
: isWeekend
? "border-dashed border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)]"
: "border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:border-[var(--color-accent)] hover:!text-[var(--color-text)]",
isDisabled ? "cursor-not-allowed opacity-45" : "",
].join(" ")}
onClick={() => {
if (isDisabled) return;
onDateChange(dateKey);
setIsCalendarOpen(false);
}}
>
<span>{dayNumber}</span>
{isWeekend ? (
<span
aria-hidden="true"
className="absolute inset-x-2 top-1/2 h-px -rotate-12 bg-[var(--color-text-muted)] opacity-70"
/>
) : null}
</Button>
);
})}
</div>
<p className="mt-2 text-xs text-[var(--color-text-muted)]">
Выходные отмечены пунктиром и недоступны.
</p>
</div>
) : null}
</div>
<div className={timeClassName}>
{timeOptions.map((option) => (
<Button
key={option}
variant="ghost"
aria-pressed={selectedTime === option}
className={[
"min-h-[54px] rounded-2xl border px-4 text-left text-sm font-medium transition",
selectedTime === option
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)] !text-[var(--color-text)]"
: "border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:border-[var(--color-accent)] hover:!text-[var(--color-text)]",
].join(" ")}
onClick={() => onTimeChange(option)}
>
{option}
</Button>
))}
</div>
</div>
);
};
export { CalendarWidget, formatDateForDisplay };