4.18.0: Улучшен календарь выбора даты
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m33s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m33s
This commit is contained in:
48
play-life-web/package-lock.json
generated
48
play-life-web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
<div className="task-postpone-modal-overlay" onClick={handlePostponeClose}>
|
||||
<div className="task-postpone-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="task-postpone-modal-header">
|
||||
@@ -995,41 +1018,19 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
</button>
|
||||
</div>
|
||||
<div className="task-postpone-modal-content">
|
||||
<div className="task-postpone-input-group">
|
||||
<input
|
||||
ref={dateInputRef}
|
||||
type="date"
|
||||
value={postponeDate}
|
||||
onChange={(e) => setPostponeDate(e.target.value)}
|
||||
className="task-postpone-input"
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
<div className="task-postpone-calendar">
|
||||
<DayPicker
|
||||
mode="single"
|
||||
selected={postponeDate ? new Date(postponeDate + 'T00:00:00') : undefined}
|
||||
onSelect={handleDateSelect}
|
||||
onDayClick={handleDayClick}
|
||||
disabled={{ before: (() => {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
return today
|
||||
})() }}
|
||||
locale={ru}
|
||||
/>
|
||||
<div
|
||||
className="task-postpone-display-date"
|
||||
onClick={() => {
|
||||
// Открываем календарь при клике
|
||||
if (dateInputRef.current) {
|
||||
if (typeof dateInputRef.current.showPicker === 'function') {
|
||||
dateInputRef.current.showPicker()
|
||||
} else {
|
||||
// Fallback для браузеров без showPicker
|
||||
dateInputRef.current.focus()
|
||||
dateInputRef.current.click()
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{postponeDate ? formatDateForDisplay(postponeDate) : 'Выберите дату'}
|
||||
</div>
|
||||
{postponeDate && (
|
||||
<button
|
||||
onClick={handlePostponeSubmit}
|
||||
disabled={isPostponing || !postponeDate}
|
||||
className="task-postpone-submit-checkmark"
|
||||
>
|
||||
✓
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="task-postpone-quick-buttons">
|
||||
{!isToday && (
|
||||
@@ -1055,6 +1056,10 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return typeof document !== 'undefined'
|
||||
? createPortal(modalContent, document.body)
|
||||
: modalContent
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user