From 4d4e84b5edad94f5e45346041f31584d15d68a9c Mon Sep 17 00:00:00 2001 From: houseassassin Date: Mon, 11 May 2026 08:51:06 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20calendar=20=E2=80=94=20open/edit=20orde?= =?UTF-8?q?rs=20and=20add=20new=20order=20with=20pre-filled=20date?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/admin/calendar/CalendarPage.jsx | 77 +++++++++++++++++++++++------ src/admin/calendar/useCalendar.js | 16 ++++-- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/src/admin/calendar/CalendarPage.jsx b/src/admin/calendar/CalendarPage.jsx index dd94cc0..8e03657 100644 --- a/src/admin/calendar/CalendarPage.jsx +++ b/src/admin/calendar/CalendarPage.jsx @@ -1,7 +1,8 @@ import { useState } from 'react'; -import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Plus } from 'lucide-react'; import { useCalendar } from './useCalendar'; import { useIsMobile } from '../../hooks/useIsMobile'; +import OrderModal from '../orders/OrderModal'; const MONTH_NAMES = [ 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', @@ -41,10 +42,11 @@ function isSameDay(a, b) { a.getDate() === b.getDate(); } -function OrderChip({ order }) { +function OrderChip({ order, onClick }) { const color = getStatusColor(order.status); return (
{ e.stopPropagation(); onClick(order); }} title={`${order.name} — ${order.status}`} style={{ background: color + '1A', @@ -58,6 +60,7 @@ function OrderChip({ order }) { textOverflow: 'ellipsis', maxWidth: '100%', lineHeight: 1.4, + cursor: 'pointer', }} > {order.name} @@ -65,7 +68,7 @@ function OrderChip({ order }) { ); } -function DayCell({ date, orders, isCurrentMonth, isToday, isSelected, onSelect, isMobile }) { +function DayCell({ date, orders, isCurrentMonth, isToday, isSelected, onSelect, onOrderClick, isMobile }) { const dayOrders = orders.filter((o) => o.scheduled_at && isSameDay(new Date(o.scheduled_at), date)); const visible = dayOrders.slice(0, 2); const extra = dayOrders.length - visible.length; @@ -110,7 +113,7 @@ function DayCell({ date, orders, isCurrentMonth, isToday, isSelected, onSelect, {!isMobile && visible.map((o) => ( - + ))} {isMobile && dayOrders.length > 0 && ( @@ -137,25 +140,23 @@ function DayCell({ date, orders, isCurrentMonth, isToday, isSelected, onSelect, ); } -function DayDetail({ date, orders }) { +function DayDetail({ date, orders, onOrderClick, onAdd }) { const dayOrders = orders.filter((o) => o.scheduled_at && isSameDay(new Date(o.scheduled_at), date)); - if (dayOrders.length === 0) { - return ( -
- На этот день записей нет -
- ); - } - return (
+ {dayOrders.length === 0 && ( +
+ На этот день записей нет +
+ )} {dayOrders.map((o) => { const color = getStatusColor(o.status); const time = new Date(o.scheduled_at).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); return (
onOrderClick(o)} className="glass-panel" style={{ padding: '12px 16px', @@ -164,7 +165,11 @@ function DayDetail({ date, orders }) { alignItems: 'center', justifyContent: 'space-between', gap: '12px', + cursor: 'pointer', + transition: 'box-shadow 0.15s', }} + onMouseEnter={(e) => e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)'} + onMouseLeave={(e) => e.currentTarget.style.boxShadow = ''} >
@@ -194,6 +199,17 @@ function DayDetail({ date, orders }) {
); })} +
); } @@ -203,8 +219,13 @@ const CalendarPage = () => { const [year, setYear] = useState(today.getFullYear()); const [month, setMonth] = useState(today.getMonth()); const [selectedDate, setSelectedDate] = useState(today); + const [editOrder, setEditOrder] = useState(null); + const [createDate, setCreateDate] = useState(null); const isMobile = useIsMobile(); - const { orders, loading } = useCalendar(year, month); + const { orders, loading, refetch } = useCalendar(year, month); + + const handleModalClose = () => { setEditOrder(null); setCreateDate(null); }; + const handleModalSaved = () => { handleModalClose(); refetch(); }; const prevMonth = () => { if (month === 0) { setYear((y) => y - 1); setMonth(11); } @@ -317,6 +338,7 @@ const CalendarPage = () => { isToday={isSameDay(date, today)} isSelected={isSameDay(date, selectedDate)} onSelect={setSelectedDate} + onOrderClick={setEditOrder} isMobile={isMobile} /> ))} @@ -333,8 +355,33 @@ const CalendarPage = () => { }}> {selectedDateLabel} - {loading ? null : } + {loading ? null : ( + setCreateDate(selectedDate)} + /> + )}
+ + {editOrder && ( + + )} + + {createDate && ( + + )}
); }; diff --git a/src/admin/calendar/useCalendar.js b/src/admin/calendar/useCalendar.js index dac9f48..5b5b9e1 100644 --- a/src/admin/calendar/useCalendar.js +++ b/src/admin/calendar/useCalendar.js @@ -1,11 +1,16 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { supabase } from '../../lib/supabase'; export function useCalendar(year, month) { const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); + const [trigger, setTrigger] = useState(0); + + const refetch = useCallback(() => setTrigger((t) => t + 1), []); useEffect(() => { + let isMounted = true; + setLoading(true); const from = new Date(year, month, 1).toISOString(); const to = new Date(year, month + 1, 0, 23, 59, 59).toISOString(); @@ -16,10 +21,11 @@ export function useCalendar(year, month) { .lte('scheduled_at', to) .order('scheduled_at', { ascending: true }) .then(({ data }) => { - setOrders(data || []); - setLoading(false); + if (isMounted) { setOrders(data || []); setLoading(false); } }); - }, [year, month]); - return { orders, loading }; + return () => { isMounted = false; }; + }, [year, month, trigger]); + + return { orders, loading, refetch }; }