feat: замена period_type на start_date в wishlist, обновление UI формы условий
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m5s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m5s
- Добавлена миграция 020 для замены period_type на start_date в score_conditions - Обновлена функция подсчёта баллов: calculateProjectPointsFromDate вместо calculateProjectPointsForPeriod - Добавлен компонент DateSelector для выбора даты начала подсчёта - По умолчанию выбран тип условия 'Баллы' - Переименованы опции: 'Баллы' и 'Задача' - Версия: 3.9.3
This commit is contained in:
@@ -166,16 +166,14 @@ function WishlistDetail({ wishlistId, onNavigate, onRefresh }) {
|
||||
const requiredPoints = condition.required_points || 0
|
||||
const currentPoints = condition.current_points || 0
|
||||
const project = condition.project_name || 'Проект'
|
||||
let period = ''
|
||||
if (condition.period_type) {
|
||||
const periodLabels = {
|
||||
week: 'за неделю',
|
||||
month: 'за месяц',
|
||||
year: 'за год',
|
||||
}
|
||||
period = ' ' + periodLabels[condition.period_type] || ''
|
||||
let dateText = ''
|
||||
if (condition.start_date) {
|
||||
const date = new Date(condition.start_date + 'T00:00:00')
|
||||
dateText = ` с ${date.toLocaleDateString('ru-RU')}`
|
||||
} else {
|
||||
dateText = ' за всё время'
|
||||
}
|
||||
conditionText = `${requiredPoints} в ${project}${period}`
|
||||
conditionText = `${requiredPoints} в ${project}${dateText}`
|
||||
progress = {
|
||||
type: 'points',
|
||||
current: currentPoints,
|
||||
|
||||
@@ -383,3 +383,67 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Date Selector Styles (аналогично task-postpone-input-group) */
|
||||
.date-selector-input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.date-selector-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.date-selector-display-date {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 1rem;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: #1f2937;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.date-selector-display-date:hover {
|
||||
border-color: #3498db;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.date-selector-display-date:active {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.date-selector-clear-button {
|
||||
padding: 0.5rem;
|
||||
background: #e5e7eb;
|
||||
color: #6b7280;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.date-selector-clear-button:hover {
|
||||
background: #d1d5db;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.date-selector-clear-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import Cropper from 'react-easy-crop'
|
||||
import { useAuth } from './auth/AuthContext'
|
||||
import Toast from './Toast'
|
||||
@@ -80,7 +80,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
|
||||
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null,
|
||||
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null,
|
||||
required_points: cond.required_points || null,
|
||||
period_type: cond.period_type || null,
|
||||
start_date: cond.start_date || null,
|
||||
display_order: idx,
|
||||
})))
|
||||
}
|
||||
@@ -289,7 +289,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
|
||||
task_id: cond.type === 'task_completion' ? cond.task_id : null,
|
||||
project_id: cond.type === 'project_points' ? cond.project_id : null,
|
||||
required_points: cond.type === 'project_points' ? parseFloat(cond.required_points) : null,
|
||||
period_type: cond.type === 'project_points' ? cond.period_type : null,
|
||||
start_date: cond.type === 'project_points' ? cond.start_date : null,
|
||||
})),
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
|
||||
<span>
|
||||
{cond.type === 'task_completion'
|
||||
? `Задача: ${tasks.find(t => t.id === cond.task_id)?.name || 'Не выбрана'}`
|
||||
: `Баллы: ${cond.required_points} в ${projects.find(p => p.project_id === cond.project_id)?.project_name || 'Не выбран'}${cond.period_type ? ` за ${cond.period_type === 'week' ? 'неделю' : cond.period_type === 'month' ? 'месяц' : 'год'}` : ''}`}
|
||||
: `Баллы: ${cond.required_points} в ${projects.find(p => p.project_id === cond.project_id)?.project_name || 'Не выбран'}${cond.start_date ? ` с ${new Date(cond.start_date + 'T00:00:00').toLocaleDateString('ru-RU')}` : ' за всё время'}`}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
@@ -549,13 +549,91 @@ function WishlistForm({ onNavigate, wishlistId }) {
|
||||
)
|
||||
}
|
||||
|
||||
// Компонент селектора даты с календарём (аналогично TaskList)
|
||||
function DateSelector({ value, onChange, placeholder = "За всё время" }) {
|
||||
const dateInputRef = useRef(null)
|
||||
|
||||
const formatDateForDisplay = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr + 'T00:00:00')
|
||||
const now = new Date()
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
|
||||
const diffDays = Math.floor((targetDate - today) / (1000 * 60 * 60 * 24))
|
||||
|
||||
const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
|
||||
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
|
||||
|
||||
if (diffDays === 0) {
|
||||
return 'Сегодня'
|
||||
} else if (diffDays === 1) {
|
||||
return 'Завтра'
|
||||
} else if (diffDays === -1) {
|
||||
return 'Вчера'
|
||||
} else if (diffDays > 1 && diffDays <= 7) {
|
||||
const dayOfWeek = targetDate.getDay()
|
||||
const dayNames = ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота']
|
||||
return dayNames[dayOfWeek]
|
||||
} else if (targetDate.getFullYear() === now.getFullYear()) {
|
||||
return `${targetDate.getDate()} ${monthNames[targetDate.getMonth()]}`
|
||||
} else {
|
||||
return `${targetDate.getDate()} ${monthNames[targetDate.getMonth()]} ${targetDate.getFullYear()}`
|
||||
}
|
||||
}
|
||||
|
||||
const handleDisplayClick = () => {
|
||||
if (dateInputRef.current) {
|
||||
if (typeof dateInputRef.current.showPicker === 'function') {
|
||||
dateInputRef.current.showPicker()
|
||||
} else {
|
||||
dateInputRef.current.focus()
|
||||
dateInputRef.current.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleClear = (e) => {
|
||||
e.stopPropagation()
|
||||
onChange('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="date-selector-input-group">
|
||||
<input
|
||||
ref={dateInputRef}
|
||||
type="date"
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value || '')}
|
||||
className="date-selector-input"
|
||||
/>
|
||||
<div
|
||||
className="date-selector-display-date"
|
||||
onClick={handleDisplayClick}
|
||||
>
|
||||
{value ? formatDateForDisplay(value) : placeholder}
|
||||
</div>
|
||||
{value && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="date-selector-clear-button"
|
||||
aria-label="Очистить дату"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Компонент формы условия разблокировки
|
||||
function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
|
||||
const [type, setType] = useState('task_completion')
|
||||
const [type, setType] = useState('project_points')
|
||||
const [taskId, setTaskId] = useState('')
|
||||
const [projectId, setProjectId] = useState('')
|
||||
const [requiredPoints, setRequiredPoints] = useState('')
|
||||
const [periodType, setPeriodType] = useState('')
|
||||
const [startDate, setStartDate] = useState('')
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
@@ -574,15 +652,15 @@ function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
|
||||
task_id: type === 'task_completion' ? parseInt(taskId) : null,
|
||||
project_id: type === 'project_points' ? parseInt(projectId) : null,
|
||||
required_points: type === 'project_points' ? parseFloat(requiredPoints) : null,
|
||||
period_type: type === 'project_points' && periodType ? periodType : null,
|
||||
start_date: type === 'project_points' && startDate ? startDate : null,
|
||||
}
|
||||
onSubmit(condition)
|
||||
// Сброс формы
|
||||
setType('task_completion')
|
||||
setType('project_points')
|
||||
setTaskId('')
|
||||
setProjectId('')
|
||||
setRequiredPoints('')
|
||||
setPeriodType('')
|
||||
setStartDate('')
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -597,8 +675,8 @@ function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
|
||||
onChange={(e) => setType(e.target.value)}
|
||||
className="form-input"
|
||||
>
|
||||
<option value="task_completion">Выполнить задачу</option>
|
||||
<option value="project_points">Набрать баллы в проекте</option>
|
||||
<option value="project_points">Баллы</option>
|
||||
<option value="task_completion">Задача</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -649,17 +727,12 @@ function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Период</label>
|
||||
<select
|
||||
value={periodType}
|
||||
onChange={(e) => setPeriodType(e.target.value)}
|
||||
className="form-input"
|
||||
>
|
||||
<option value="">За всё время</option>
|
||||
<option value="week">За неделю</option>
|
||||
<option value="month">За месяц</option>
|
||||
<option value="year">За год</option>
|
||||
</select>
|
||||
<label>Дата начала подсчёта</label>
|
||||
<DateSelector
|
||||
value={startDate}
|
||||
onChange={setStartDate}
|
||||
placeholder="За всё время"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user