From 09ab87b6dde2eccf6280a7392e922fa85e7e2b58 Mon Sep 17 00:00:00 2001 From: poignatov Date: Wed, 4 Feb 2026 21:13:29 +0300 Subject: [PATCH] =?UTF-8?q?4.18.0:=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=B0=D0=BB=D0=B5=D0=BD=D0=B4=D0=B0=D1=80=D1=8C?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- play-life-web/package-lock.json | 48 +++++++- play-life-web/package.json | 3 +- play-life-web/src/components/TaskList.css | 129 +++++++++++++++++++++- play-life-web/src/components/TaskList.jsx | 77 +++++++------ 5 files changed, 217 insertions(+), 42 deletions(-) diff --git a/VERSION b/VERSION index 1b0a87f..b30c4dc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.17.1 +4.18.0 diff --git a/play-life-web/package-lock.json b/play-life-web/package-lock.json index 4d453a8..29486a9 100644 --- a/play-life-web/package-lock.json +++ b/play-life-web/package-lock.json @@ -1,12 +1,12 @@ { "name": "play-life-web", - "version": "3.28.1", + "version": "4.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "play-life-web", - "version": "3.28.1", + "version": "4.17.1", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -15,6 +15,7 @@ "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-circular-progressbar": "^2.2.0", + "react-day-picker": "^9.13.0", "react-dom": "^18.2.0", "react-easy-crop": "^5.5.6" }, @@ -1618,6 +1619,12 @@ "node": ">=6.9.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -3871,6 +3878,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -5923,6 +5946,27 @@ "react": ">=0.14.0" } }, + "node_modules/react-day-picker": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz", + "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", diff --git a/play-life-web/package.json b/play-life-web/package.json index cac38eb..7c9afee 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "4.17.1", + "version": "4.18.0", "type": "module", "scripts": { "dev": "vite", @@ -15,6 +15,7 @@ "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-circular-progressbar": "^2.2.0", + "react-day-picker": "^9.13.0", "react-dom": "^18.2.0", "react-easy-crop": "^5.5.6" }, diff --git a/play-life-web/src/components/TaskList.css b/play-life-web/src/components/TaskList.css index c2b88d0..2080e88 100644 --- a/play-life-web/src/components/TaskList.css +++ b/play-life-web/src/components/TaskList.css @@ -277,7 +277,7 @@ } .task-postpone-modal-overlay { - position: fixed; + position: fixed !important; top: 0; left: 0; right: 0; @@ -286,7 +286,8 @@ display: flex; align-items: center; justify-content: center; - z-index: 1000; + z-index: 9999 !important; + padding: 1rem; } .task-postpone-modal { @@ -336,6 +337,130 @@ padding: 0 1.5rem 1.5rem 1.5rem; } +.task-postpone-calendar { + margin-bottom: 1rem; +} + +/* Стили для react-day-picker v9 */ +.task-postpone-calendar .rdp { + --rdp-cell-size: 40px; + --rdp-accent-color: #9ca3af; + --rdp-accent-background-color: #9ca3af; + --rdp-background-color: transparent; + --rdp-outline: none; + --rdp-outline-selected: none; + --rdp-selected-border: none; + --rdp-selected-font: inherit; + margin: 0; +} + +/* Ячейка дня */ +.task-postpone-calendar .rdp-day { + border-radius: 0.375rem; + transition: all 0.2s; +} + +/* Кнопка внутри дня */ +.task-postpone-calendar .rdp-day_button { + border-radius: 0.375rem; + transition: all 0.2s; +} + +.task-postpone-calendar .rdp-day_button:hover:not([disabled]) { + background-color: #f3f4f6; +} + +/* Сегодняшняя дата - жирный текст */ +.task-postpone-calendar .rdp-today .rdp-day_button, +.task-postpone-calendar .rdp-day.rdp-today button, +.task-postpone-calendar [data-today="true"] button, +.task-postpone-calendar .rdp [data-today] button { + font-weight: 700 !important; + color: #1f2937; +} + +/* Выбранная дата - серый маленький залитый круг */ +.task-postpone-calendar .rdp-selected { + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.task-postpone-calendar .rdp-selected .rdp-day_button { + border: none !important; + border-radius: 50% !important; + background-color: #e5e7eb !important; + color: #6b7280 !important; + font-weight: 400 !important; + outline: none !important; + box-shadow: none !important; + cursor: pointer; + width: 28px !important; + height: 28px !important; + min-width: 28px !important; + min-height: 28px !important; + padding: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.task-postpone-calendar .rdp-selected .rdp-day_button:hover { + background-color: #d1d5db !important; +} + +/* Если дата одновременно сегодняшняя и выбранная */ +.task-postpone-calendar .rdp-today.rdp-selected .rdp-day_button { + color: #6b7280 !important; + background-color: #e5e7eb !important; + font-weight: 700 !important; +} + +/* Недоступные даты (прошлые) */ +.task-postpone-calendar .rdp-disabled .rdp-day_button { + opacity: 1 !important; + cursor: not-allowed; + color: #9ca3af !important; + background-color: transparent !important; +} + +.task-postpone-calendar .rdp-disabled .rdp-day_button:hover { + background-color: transparent !important; +} + +/* Дни из других месяцев */ +.task-postpone-calendar .rdp-outside .rdp-day_button { + opacity: 0.3; + color: #9ca3af; +} + +/* Заголовок календаря */ +.task-postpone-calendar .rdp-caption_label { + font-size: 1rem; + font-weight: 600; + color: #1f2937; +} + +.task-postpone-calendar .rdp-nav_button { + border-radius: 0.375rem; + transition: all 0.2s; +} + +.task-postpone-calendar .rdp-nav_button:hover { + background-color: #f3f4f6; +} + +/* Адаптивность для мобильных устройств */ +@media (max-width: 640px) { + .task-postpone-calendar .rdp { + --rdp-cell-size: 36px; + } + + .task-postpone-calendar .rdp-caption_label { + font-size: 0.875rem; + } +} + .task-postpone-quick-buttons { display: flex; gap: 0.5rem; diff --git a/play-life-web/src/components/TaskList.jsx b/play-life-web/src/components/TaskList.jsx index 1e89d29..5892ae3 100644 --- a/play-life-web/src/components/TaskList.jsx +++ b/play-life-web/src/components/TaskList.jsx @@ -1,8 +1,12 @@ import React, { useState, useEffect, useMemo, useRef } from 'react' +import { createPortal } from 'react-dom' import { useAuth } from './auth/AuthContext' import TaskDetail from './TaskDetail' import LoadingError from './LoadingError' import Toast from './Toast' +import { DayPicker } from 'react-day-picker' +import { ru } from 'react-day-picker/locale' +import 'react-day-picker/style.css' import './TaskList.css' const API_URL = '/api/tasks' @@ -19,7 +23,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry const [isPostponing, setIsPostponing] = useState(false) const [toast, setToast] = useState(null) const [searchQuery, setSearchQuery] = useState('') - const dateInputRef = useRef(null) useEffect(() => { if (data) { @@ -368,6 +371,26 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry setPostponeDate('') } + const handleDateSelect = (date) => { + if (!date) return + + // Преобразуем дату в формат YYYY-MM-DD + const formattedDate = formatDateToLocal(date) + setPostponeDate(formattedDate) + + // Применяем дату и закрываем модальное окно + if (selectedTaskForPostpone) { + handlePostponeSubmitWithDate(formattedDate) + } + } + + const handleDayClick = (day, modifiers) => { + // Обрабатываем клик даже если дата уже выбрана + if (day && !modifiers.disabled) { + handleDateSelect(day) + } + } + const handleTodayClick = () => { const today = new Date() today.setHours(0, 0, 0, 0) @@ -985,7 +1008,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry const isToday = nextShowAtStr === todayStr const isTomorrow = nextShowAtStr === tomorrowStr - return ( + const modalContent = (
e.stopPropagation()}>
@@ -995,41 +1018,19 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
-
- setPostponeDate(e.target.value)} - className="task-postpone-input" - min={new Date().toISOString().split('T')[0]} +
+ { + const today = new Date() + today.setHours(0, 0, 0, 0) + return today + })() }} + locale={ru} /> -
{ - // Открываем календарь при клике - if (dateInputRef.current) { - if (typeof dateInputRef.current.showPicker === 'function') { - dateInputRef.current.showPicker() - } else { - // Fallback для браузеров без showPicker - dateInputRef.current.focus() - dateInputRef.current.click() - } - } - }} - > - {postponeDate ? formatDateForDisplay(postponeDate) : 'Выберите дату'} -
- {postponeDate && ( - - )}
{!isToday && ( @@ -1055,6 +1056,10 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
) + + return typeof document !== 'undefined' + ? createPortal(modalContent, document.body) + : modalContent })()}
)