6.14.0: Еженедельное подтверждение приоритетов
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s

This commit is contained in:
poignatov
2026-03-12 17:16:57 +03:00
parent 1df00bbefd
commit 64493b9c1f
10 changed files with 411 additions and 81 deletions

View File

@@ -27,10 +27,10 @@ import './Integrations.css'
// API endpoints (используем относительные пути, проксирование настроено в nginx/vite)
const PROJECTS_API_URL = '/projects'
const PRIORITY_UPDATE_API_URL = '/project/priority'
const PROJECT_COLOR_API_URL = '/project/color'
const PROJECT_MOVE_API_URL = '/project/move'
const PROJECT_CREATE_API_URL = '/project/create'
const PRIORITIES_CONFIRM_API_URL = '/priorities/confirm'
// Компонент экрана добавления проекта
function AddProjectScreen({ onClose, onSuccess, onError }) {
@@ -387,13 +387,14 @@ function PrioritySlot({ title, projects, allProjects, onMenuClick, maxItems = nu
)
}
function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad, onLoadingChange, onErrorChange, refreshTrigger, onNavigate }) {
function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad, onLoadingChange, onErrorChange, refreshTrigger, onNavigate, onConfirmed, onClose }) {
const { authFetch } = useAuth()
const [projectsLoading, setProjectsLoading] = useState(false)
const [projectsError, setProjectsError] = useState(null)
const [hasDataCache, setHasDataCache] = useState(false) // Отслеживаем наличие кеша
const [toastMessage, setToastMessage] = useState(null)
const [isSaving, setIsSaving] = useState(false)
// Уведомляем родительский компонент об изменении состояния загрузки
useEffect(() => {
if (onLoadingChange) {
@@ -421,9 +422,8 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
const scrollContainerRef = useRef(null)
const hasFetchedRef = useRef(false)
const skipNextEffectRef = useRef(false)
const lastRefreshTriggerRef = useRef(0) // Отслеживаем последний обработанный refreshTrigger
const isLoadingRef = useRef(false) // Отслеживаем, идет ли сейчас загрузка
const lastRefreshTriggerRef = useRef(0)
const isLoadingRef = useRef(false)
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -608,60 +608,30 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
return map
}, [lowPriority, maxPriority, mediumPriority])
const prevAssignmentsRef = useRef(new Map())
const initializedAssignmentsRef = useRef(false)
const handleSave = useCallback(async () => {
const assignments = buildAssignments()
const changes = []
assignments.forEach(({ id, priority }) => {
if (id) changes.push({ id, priority })
})
const sendPriorityChanges = useCallback(async (changes) => {
if (!changes.length) return
setIsSaving(true)
try {
await authFetch(PRIORITY_UPDATE_API_URL, {
const response = await authFetch(PRIORITIES_CONFIRM_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(changes),
})
} catch (e) {
console.error('Ошибка отправки изменений приоритета', e)
}
}, [])
useEffect(() => {
const current = buildAssignments()
if (!initializedAssignmentsRef.current) {
prevAssignmentsRef.current = current
initializedAssignmentsRef.current = true
return
}
if (skipNextEffectRef.current) {
skipNextEffectRef.current = false
prevAssignmentsRef.current = current
return
}
const prev = prevAssignmentsRef.current
const allKeys = new Set([...prev.keys(), ...current.keys()])
const changes = []
allKeys.forEach(key => {
const prevItem = prev.get(key)
const currItem = current.get(key)
const prevPriority = prevItem?.priority ?? null
const currPriority = currItem?.priority ?? null
const id = currItem?.id ?? prevItem?.id
if (!id) return
if (prevPriority !== currPriority) {
changes.push({ id, priority: currPriority })
if (!response.ok) {
throw new Error('Ошибка сохранения')
}
})
if (changes.length) {
sendPriorityChanges(changes)
if (onConfirmed) onConfirmed()
} catch (e) {
setToastMessage({ text: e.message || 'Ошибка сохранения', type: 'error' })
} finally {
setIsSaving(false)
}
prevAssignmentsRef.current = current
}, [buildAssignments, sendPriorityChanges])
}, [authFetch, buildAssignments, onConfirmed])
const findProjectContainer = (projectName) => {
if (maxPriority.find(p => p.name === projectName)) return 'max'
@@ -919,9 +889,9 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
return (
<div className="max-w-2xl mx-auto flex flex-col h-full">
{onNavigate && (
{(onNavigate || onClose) && (
<button
onClick={() => window.history.back()}
onClick={() => onClose ? onClose() : window.history.back()}
className="close-x-button"
title="Закрыть"
>
@@ -1090,6 +1060,21 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
/>
)}
<div className={onClose
? 'sticky bottom-0 pt-2 pb-4 mt-2'
: 'fixed bottom-0 left-0 right-0 bg-gray-100 px-4 pt-2 pb-4'
}>
<div className="max-w-2xl mx-auto">
<button
onClick={handleSave}
disabled={isSaving}
className="w-full py-3 bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-400 text-white font-semibold rounded-lg shadow transition-all"
>
{isSaving ? 'Сохранение...' : 'Сохранить'}
</button>
</div>
</div>
{toastMessage && (
<Toast
message={toastMessage.text}

View File

@@ -30,6 +30,8 @@ function getLastFiveWeeks() {
return weeks.reverse() // От старой к новой
}
// Проверяет, закончилась ли уже данная ISO-неделя (текущая неделя не закончилась)
function Tracking({ onNavigate, activeTab }) {
const { authFetch } = useAuth()
const [weeks, setWeeks] = useState(() => getLastFiveWeeks())
@@ -147,7 +149,7 @@ function Tracking({ onNavigate, activeTab }) {
) : (
<div className="users-list">
{data?.users.map(user => (
<UserTrackingCard key={user.user_id} user={user} />
<UserTrackingCard key={user.user_id} user={user} selectedWeek={selectedWeek} />
))}
</div>
)}
@@ -156,7 +158,7 @@ function Tracking({ onNavigate, activeTab }) {
}
// Карточка пользователя с прогрессом
function UserTrackingCard({ user }) {
function UserTrackingCard({ user, selectedWeek }) {
// Сортируем проекты по priority (1, 2, остальные)
const sortedProjects = [...user.projects].sort((a, b) => {
const pa = a.priority ?? 99
@@ -169,10 +171,27 @@ function UserTrackingCard({ user }) {
return percent >= 100 ? 'percent-green' : 'percent-blue'
}
// Показываем (черновик) если выбранная неделя позже недели подтверждения
const showDraft = selectedWeek && (() => {
const cy = user.priorities_confirmed_year || 0
const cw = user.priorities_confirmed_week || 0
const sy = selectedWeek.year
const sw = selectedWeek.week
// Неделя не подтверждена вообще (0,0) или выбранная неделя позже подтверждённой
return sy > cy || (sy === cy && sw > cw)
})()
return (
<div className={`user-tracking-card ${user.is_current_user ? 'current-user' : ''}`}>
<div className="user-header">
<span className="user-name">{user.user_name}</span>
<span className="user-name">
{user.user_name}
{showDraft && (
<span style={{ color: '#9ca3af', fontWeight: 'normal', fontSize: '0.85em', marginLeft: '4px' }}>
(черновик)
</span>
)}
</span>
<span className={`user-total ${getPercentColorClass(totalPercent)}`}>{totalPercent.toFixed(0)}%</span>
</div>
<div className="projects-list">