Обновление модального окна переноса задачи (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",
|
"name": "play-life-web",
|
||||||
"version": "3.5.0",
|
"version": "3.5.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -220,23 +220,43 @@
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-task-name {
|
.task-postpone-quick-buttons {
|
||||||
margin: 0 0 1rem 0;
|
display: flex;
|
||||||
font-size: 1rem;
|
gap: 0.5rem;
|
||||||
color: #1f2937;
|
margin-top: 0.5rem;
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-label {
|
.task-postpone-quick-button {
|
||||||
display: flex;
|
padding: 0.5rem 1rem;
|
||||||
flex-direction: column;
|
border: 1px solid #d1d5db;
|
||||||
gap: 0.5rem;
|
border-radius: 0.375rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #374151;
|
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 {
|
.task-postpone-input {
|
||||||
|
flex: 1;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
@@ -250,43 +270,25 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-modal-actions {
|
.task-postpone-submit-checkmark {
|
||||||
padding: 1rem 1.5rem 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-postpone-cancel-button,
|
|
||||||
.task-postpone-submit-button {
|
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
|
background: linear-gradient(to right, #6366f1, #8b5cf6);
|
||||||
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
min-width: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-cancel-button {
|
.task-postpone-submit-checkmark:hover:not(:disabled) {
|
||||||
background: #f3f4f6;
|
transform: translateY(-1px);
|
||||||
color: #374151;
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-cancel-button:hover:not(:disabled) {
|
.task-postpone-submit-checkmark: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 {
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
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)
|
// Форматирование даты в YYYY-MM-DD (локальное время, без смещения в UTC)
|
||||||
const formatDateToLocal = (date) => {
|
const formatDateToLocal = (date) => {
|
||||||
const year = date.getFullYear()
|
const year = date.getFullYear()
|
||||||
@@ -177,17 +225,26 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
|
|
||||||
// Устанавливаем дату по умолчанию
|
// Устанавливаем дату по умолчанию
|
||||||
let defaultDate
|
let defaultDate
|
||||||
|
const now = new Date()
|
||||||
|
now.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
if (task.repetition_date) {
|
if (task.repetition_date) {
|
||||||
// Для задач с repetition_date - вычисляем следующую подходящую дату
|
// Для задач с repetition_date - вычисляем следующую подходящую дату
|
||||||
const nextDate = calculateNextDateFromRepetitionDate(task.repetition_date)
|
const nextDate = calculateNextDateFromRepetitionDate(task.repetition_date)
|
||||||
if (nextDate) {
|
if (nextDate) {
|
||||||
defaultDate = 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) {
|
if (!defaultDate) {
|
||||||
// Без repetition_date или если не удалось вычислить - завтра
|
// Без repetition_date/repetition_period или если не удалось вычислить - завтра
|
||||||
defaultDate = new Date()
|
defaultDate = new Date(now)
|
||||||
defaultDate.setDate(defaultDate.getDate() + 1)
|
defaultDate.setDate(defaultDate.getDate() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,11 +254,42 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
|
|
||||||
const handlePostponeSubmit = async () => {
|
const handlePostponeSubmit = async () => {
|
||||||
if (!selectedTaskForPostpone || !postponeDate) return
|
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)
|
setIsPostponing(true)
|
||||||
try {
|
try {
|
||||||
// Преобразуем дату в ISO формат с временем
|
// Преобразуем дату в ISO формат с временем
|
||||||
const dateObj = new Date(postponeDate)
|
const dateObj = new Date(dateToUse)
|
||||||
dateObj.setHours(0, 0, 0, 0)
|
dateObj.setHours(0, 0, 0, 0)
|
||||||
const isoDate = dateObj.toISOString()
|
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) => {
|
const toggleCompletedExpanded = (projectName) => {
|
||||||
setExpandedCompleted(prev => ({
|
setExpandedCompleted(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -588,47 +637,75 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Модальное окно для переноса задачи */}
|
{/* Модальное окно для переноса задачи */}
|
||||||
{selectedTaskForPostpone && (
|
{selectedTaskForPostpone && (() => {
|
||||||
<div className="task-postpone-modal-overlay" onClick={handlePostponeClose}>
|
const todayStr = formatDateToLocal(new Date())
|
||||||
<div className="task-postpone-modal" onClick={(e) => e.stopPropagation()}>
|
const tomorrow = new Date()
|
||||||
<div className="task-postpone-modal-header">
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||||
<h3>Перенести задачу</h3>
|
const tomorrowStr = formatDateToLocal(tomorrow)
|
||||||
<button onClick={handlePostponeClose} className="task-postpone-close-button">
|
|
||||||
✕
|
// Проверяем next_show_at задачи, а не значение в поле ввода
|
||||||
</button>
|
let nextShowAtStr = null
|
||||||
</div>
|
if (selectedTaskForPostpone.next_show_at) {
|
||||||
<div className="task-postpone-modal-content">
|
const nextShowAtDate = new Date(selectedTaskForPostpone.next_show_at)
|
||||||
<p className="task-postpone-task-name">{selectedTaskForPostpone.name}</p>
|
nextShowAtStr = formatDateToLocal(nextShowAtDate)
|
||||||
<label className="task-postpone-label">
|
}
|
||||||
Дата показа:
|
|
||||||
<input
|
const isToday = nextShowAtStr === todayStr
|
||||||
type="date"
|
const isTomorrow = nextShowAtStr === tomorrowStr
|
||||||
value={postponeDate}
|
|
||||||
onChange={(e) => setPostponeDate(e.target.value)}
|
return (
|
||||||
className="task-postpone-input"
|
<div className="task-postpone-modal-overlay" onClick={handlePostponeClose}>
|
||||||
min={new Date().toISOString().split('T')[0]}
|
<div className="task-postpone-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
/>
|
<div className="task-postpone-modal-header">
|
||||||
</label>
|
<h3>{selectedTaskForPostpone.name}</h3>
|
||||||
</div>
|
<button onClick={handlePostponeClose} className="task-postpone-close-button">
|
||||||
<div className="task-postpone-modal-actions">
|
✕
|
||||||
<button
|
</button>
|
||||||
onClick={handlePostponeReset}
|
</div>
|
||||||
className="task-postpone-cancel-button"
|
<div className="task-postpone-modal-content">
|
||||||
disabled={isPostponing}
|
<div className="task-postpone-input-group">
|
||||||
>
|
<input
|
||||||
{isPostponing ? 'Сброс...' : 'Сбросить'}
|
type="date"
|
||||||
</button>
|
value={postponeDate}
|
||||||
<button
|
onChange={(e) => setPostponeDate(e.target.value)}
|
||||||
onClick={handlePostponeSubmit}
|
className="task-postpone-input"
|
||||||
className="task-postpone-submit-button"
|
min={new Date().toISOString().split('T')[0]}
|
||||||
disabled={isPostponing || !postponeDate}
|
/>
|
||||||
>
|
{postponeDate && (
|
||||||
{isPostponing ? 'Перенос...' : 'Перенести'}
|
<button
|
||||||
</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>
|
||||||
</div>
|
)
|
||||||
)}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user