feat: calendar — open/edit orders and add new order with pre-filled date

This commit is contained in:
2026-05-11 08:51:06 +03:00
parent bdfc0ebb82
commit 4d4e84b5ed
2 changed files with 73 additions and 20 deletions
+62 -15
View File
@@ -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 (
<div
onClick={(e) => { 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,
</span>
{!isMobile && visible.map((o) => (
<OrderChip key={o.id} order={o} />
<OrderChip key={o.id} order={o} onClick={onOrderClick} />
))}
{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 (
<div style={{ color: 'var(--text-muted, #8C9097)', fontSize: '0.85rem' }}>
На этот день записей нет
</div>
);
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{dayOrders.length === 0 && (
<div style={{ color: 'var(--text-muted, #8C9097)', fontSize: '0.85rem' }}>
На этот день записей нет
</div>
)}
{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 (
<div
key={o.id}
onClick={() => 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 = ''}
>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '0.92rem', marginBottom: '2px' }}>
@@ -194,6 +199,17 @@ function DayDetail({ date, orders }) {
</div>
);
})}
<button
onClick={onAdd}
className="btn-primary"
style={{
display: 'inline-flex', alignItems: 'center', gap: '6px',
alignSelf: 'flex-start', marginTop: '4px',
}}
>
<Plus size={15} />
Добавить заявку
</button>
</div>
);
}
@@ -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}
</h3>
{loading ? null : <DayDetail date={selectedDate} orders={orders} />}
{loading ? null : (
<DayDetail
date={selectedDate}
orders={orders}
onOrderClick={setEditOrder}
onAdd={() => setCreateDate(selectedDate)}
/>
)}
</div>
{editOrder && (
<OrderModal
order={editOrder}
mode="edit"
onClose={handleModalClose}
onSaved={handleModalSaved}
/>
)}
{createDate && (
<OrderModal
order={{ scheduled_at: createDate.toISOString() }}
mode="create"
onClose={handleModalClose}
onSaved={handleModalSaved}
/>
)}
</div>
);
};
+11 -5
View File
@@ -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 };
}