Обновление модального окна переноса задачи (v3.5.2)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 39s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 39s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "play-life-web",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -220,23 +220,43 @@
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.task-postpone-task-name {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1rem;
|
||||
color: #1f2937;
|
||||
font-weight: 500;
|
||||
.task-postpone-quick-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.task-postpone-label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
.task-postpone-quick-button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.task-postpone-quick-button:hover:not(:disabled) {
|
||||
background: #f3f4f6;
|
||||
border-color: #6366f1;
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.task-postpone-quick-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.task-postpone-input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-postpone-input {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
@@ -250,43 +270,25 @@
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
.task-postpone-modal-actions {
|
||||
padding: 1rem 1.5rem 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-postpone-cancel-button,
|
||||
.task-postpone-submit-button {
|
||||
.task-postpone-submit-checkmark {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(to right, #6366f1, #8b5cf6);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
.task-postpone-cancel-button {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
.task-postpone-submit-checkmark:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
.task-postpone-cancel-button:hover:not(:disabled) {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.task-postpone-submit-button {
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.task-postpone-submit-button:hover:not(:disabled) {
|
||||
background: #4f46e5;
|
||||
}
|
||||
|
||||
.task-postpone-submit-button:disabled {
|
||||
.task-postpone-submit-checkmark:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -163,6 +163,54 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для вычисления следующей даты по repetition_period
|
||||
const calculateNextDateFromRepetitionPeriod = (repetitionPeriodStr) => {
|
||||
if (!repetitionPeriodStr) return null
|
||||
|
||||
const parts = repetitionPeriodStr.trim().split(/\s+/)
|
||||
if (parts.length < 2) return null
|
||||
|
||||
const value = parseInt(parts[0], 10)
|
||||
if (isNaN(value) || value === 0) return null
|
||||
|
||||
const unit = parts[1].toLowerCase()
|
||||
const now = new Date()
|
||||
now.setHours(0, 0, 0, 0)
|
||||
|
||||
const nextDate = new Date(now)
|
||||
|
||||
switch (unit) {
|
||||
case 'minute':
|
||||
case 'minutes':
|
||||
nextDate.setMinutes(nextDate.getMinutes() + value)
|
||||
break
|
||||
case 'hour':
|
||||
case 'hours':
|
||||
nextDate.setHours(nextDate.getHours() + value)
|
||||
break
|
||||
case 'day':
|
||||
case 'days':
|
||||
nextDate.setDate(nextDate.getDate() + value)
|
||||
break
|
||||
case 'week':
|
||||
case 'weeks':
|
||||
nextDate.setDate(nextDate.getDate() + value * 7)
|
||||
break
|
||||
case 'month':
|
||||
case 'months':
|
||||
nextDate.setMonth(nextDate.getMonth() + value)
|
||||
break
|
||||
case 'year':
|
||||
case 'years':
|
||||
nextDate.setFullYear(nextDate.getFullYear() + value)
|
||||
break
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
return nextDate
|
||||
}
|
||||
|
||||
// Форматирование даты в YYYY-MM-DD (локальное время, без смещения в UTC)
|
||||
const formatDateToLocal = (date) => {
|
||||
const year = date.getFullYear()
|
||||
@@ -177,17 +225,26 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
||||
|
||||
// Устанавливаем дату по умолчанию
|
||||
let defaultDate
|
||||
const now = new Date()
|
||||
now.setHours(0, 0, 0, 0)
|
||||
|
||||
if (task.repetition_date) {
|
||||
// Для задач с repetition_date - вычисляем следующую подходящую дату
|
||||
const nextDate = calculateNextDateFromRepetitionDate(task.repetition_date)
|
||||
if (nextDate) {
|
||||
defaultDate = nextDate
|
||||
}
|
||||
} else if (task.repetition_period && !isZeroPeriod(task.repetition_period)) {
|
||||
// Для задач с repetition_period (не нулевым) - вычисляем следующую дату
|
||||
const nextDate = calculateNextDateFromRepetitionPeriod(task.repetition_period)
|
||||
if (nextDate) {
|
||||
defaultDate = nextDate
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultDate) {
|
||||
// Без repetition_date или если не удалось вычислить - завтра
|
||||
defaultDate = new Date()
|
||||
// Без repetition_date/repetition_period или если не удалось вычислить - завтра
|
||||
defaultDate = new Date(now)
|
||||
defaultDate.setDate(defaultDate.getDate() + 1)
|
||||
}
|
||||
|
||||
@@ -197,11 +254,42 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
||||
|
||||
const handlePostponeSubmit = async () => {
|
||||
if (!selectedTaskForPostpone || !postponeDate) return
|
||||
await handlePostponeSubmitWithDate(postponeDate)
|
||||
}
|
||||
|
||||
const handlePostponeClose = () => {
|
||||
setSelectedTaskForPostpone(null)
|
||||
setPostponeDate('')
|
||||
}
|
||||
|
||||
const handleTodayClick = () => {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
setPostponeDate(formatDateToLocal(today))
|
||||
// Применяем дату сразу
|
||||
if (selectedTaskForPostpone) {
|
||||
handlePostponeSubmitWithDate(formatDateToLocal(today))
|
||||
}
|
||||
}
|
||||
|
||||
const handleTomorrowClick = () => {
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
tomorrow.setHours(0, 0, 0, 0)
|
||||
setPostponeDate(formatDateToLocal(tomorrow))
|
||||
// Применяем дату сразу
|
||||
if (selectedTaskForPostpone) {
|
||||
handlePostponeSubmitWithDate(formatDateToLocal(tomorrow))
|
||||
}
|
||||
}
|
||||
|
||||
const handlePostponeSubmitWithDate = async (dateToUse) => {
|
||||
if (!selectedTaskForPostpone || !dateToUse) return
|
||||
|
||||
setIsPostponing(true)
|
||||
try {
|
||||
// Преобразуем дату в ISO формат с временем
|
||||
const dateObj = new Date(postponeDate)
|
||||
const dateObj = new Date(dateToUse)
|
||||
dateObj.setHours(0, 0, 0, 0)
|
||||
const isoDate = dateObj.toISOString()
|
||||
|
||||
@@ -234,45 +322,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePostponeReset = async () => {
|
||||
if (!selectedTaskForPostpone) return
|
||||
|
||||
setIsPostponing(true)
|
||||
try {
|
||||
const response = await authFetch(`${API_URL}/${selectedTaskForPostpone.id}/postpone`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ next_show_at: null }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
throw new Error(errorData.message || 'Ошибка при сбросе переноса задачи')
|
||||
}
|
||||
|
||||
// Обновляем список
|
||||
if (onRefresh) {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
// Закрываем модальное окно
|
||||
setSelectedTaskForPostpone(null)
|
||||
setPostponeDate('')
|
||||
} catch (err) {
|
||||
console.error('Error resetting postpone:', err)
|
||||
alert(err.message || 'Ошибка при сбросе переноса задачи')
|
||||
} finally {
|
||||
setIsPostponing(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePostponeClose = () => {
|
||||
setSelectedTaskForPostpone(null)
|
||||
setPostponeDate('')
|
||||
}
|
||||
|
||||
const toggleCompletedExpanded = (projectName) => {
|
||||
setExpandedCompleted(prev => ({
|
||||
...prev,
|
||||
@@ -588,47 +637,75 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
||||
)}
|
||||
|
||||
{/* Модальное окно для переноса задачи */}
|
||||
{selectedTaskForPostpone && (
|
||||
<div className="task-postpone-modal-overlay" onClick={handlePostponeClose}>
|
||||
<div className="task-postpone-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="task-postpone-modal-header">
|
||||
<h3>Перенести задачу</h3>
|
||||
<button onClick={handlePostponeClose} className="task-postpone-close-button">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="task-postpone-modal-content">
|
||||
<p className="task-postpone-task-name">{selectedTaskForPostpone.name}</p>
|
||||
<label className="task-postpone-label">
|
||||
Дата показа:
|
||||
<input
|
||||
type="date"
|
||||
value={postponeDate}
|
||||
onChange={(e) => setPostponeDate(e.target.value)}
|
||||
className="task-postpone-input"
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="task-postpone-modal-actions">
|
||||
<button
|
||||
onClick={handlePostponeReset}
|
||||
className="task-postpone-cancel-button"
|
||||
disabled={isPostponing}
|
||||
>
|
||||
{isPostponing ? 'Сброс...' : 'Сбросить'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handlePostponeSubmit}
|
||||
className="task-postpone-submit-button"
|
||||
disabled={isPostponing || !postponeDate}
|
||||
>
|
||||
{isPostponing ? 'Перенос...' : 'Перенести'}
|
||||
</button>
|
||||
{selectedTaskForPostpone && (() => {
|
||||
const todayStr = formatDateToLocal(new Date())
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
const tomorrowStr = formatDateToLocal(tomorrow)
|
||||
|
||||
// Проверяем next_show_at задачи, а не значение в поле ввода
|
||||
let nextShowAtStr = null
|
||||
if (selectedTaskForPostpone.next_show_at) {
|
||||
const nextShowAtDate = new Date(selectedTaskForPostpone.next_show_at)
|
||||
nextShowAtStr = formatDateToLocal(nextShowAtDate)
|
||||
}
|
||||
|
||||
const isToday = nextShowAtStr === todayStr
|
||||
const isTomorrow = nextShowAtStr === tomorrowStr
|
||||
|
||||
return (
|
||||
<div className="task-postpone-modal-overlay" onClick={handlePostponeClose}>
|
||||
<div className="task-postpone-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="task-postpone-modal-header">
|
||||
<h3>{selectedTaskForPostpone.name}</h3>
|
||||
<button onClick={handlePostponeClose} className="task-postpone-close-button">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="task-postpone-modal-content">
|
||||
<div className="task-postpone-input-group">
|
||||
<input
|
||||
type="date"
|
||||
value={postponeDate}
|
||||
onChange={(e) => setPostponeDate(e.target.value)}
|
||||
className="task-postpone-input"
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
/>
|
||||
{postponeDate && (
|
||||
<button
|
||||
onClick={handlePostponeSubmit}
|
||||
disabled={isPostponing || !postponeDate}
|
||||
className="task-postpone-submit-checkmark"
|
||||
>
|
||||
✓
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="task-postpone-quick-buttons">
|
||||
{!isToday && (
|
||||
<button
|
||||
onClick={handleTodayClick}
|
||||
className="task-postpone-quick-button"
|
||||
disabled={isPostponing}
|
||||
>
|
||||
Сегодня
|
||||
</button>
|
||||
)}
|
||||
{!isTomorrow && (
|
||||
<button
|
||||
onClick={handleTomorrowClick}
|
||||
className="task-postpone-quick-button"
|
||||
disabled={isPostponing}
|
||||
>
|
||||
Завтра
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user