feat: добавлена возможность создания проектов через UI - версия 2.7.0
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 38s

This commit is contained in:
Play Life Bot
2026-01-02 16:09:16 +03:00
parent ccb365c95c
commit a5ce0de236
3 changed files with 191 additions and 2 deletions

View File

@@ -25,6 +25,104 @@ import { useAuth } from './auth/AuthContext'
const PROJECTS_API_URL = '/projects'
const PRIORITY_UPDATE_API_URL = '/project/priority'
const PROJECT_MOVE_API_URL = '/project/move'
const PROJECT_CREATE_API_URL = '/project/create'
// Компонент экрана добавления проекта
function AddProjectScreen({ onClose, onSuccess }) {
const { authFetch } = useAuth()
const [projectName, setProjectName] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = async () => {
if (!projectName.trim()) {
setError('Введите название проекта')
return
}
setIsSubmitting(true)
setError(null)
try {
const response = await authFetch(PROJECT_CREATE_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: projectName.trim(),
}),
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(errorText || 'Ошибка при создании проекта')
}
onSuccess()
} catch (err) {
console.error('Ошибка создания проекта:', err)
setError(err.message || 'Ошибка при создании проекта')
} finally {
setIsSubmitting(false)
}
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg max-w-md w-90 shadow-lg max-h-[90vh] flex flex-col">
{/* Заголовок с кнопкой закрытия */}
<div className="flex justify-end p-4 border-b border-gray-200">
<button
onClick={onClose}
className="flex items-center justify-center w-10 h-10 rounded-full bg-white hover:bg-gray-100 text-gray-600 hover:text-gray-800 border border-gray-200 hover:border-gray-300 transition-all duration-200 shadow-sm hover:shadow-md"
title="Закрыть"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
{/* Контент */}
<div className="flex-1 overflow-y-auto p-6">
{/* Поле ввода */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Название проекта
</label>
<input
type="text"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter' && projectName.trim() && !isSubmitting) {
handleSubmit()
}
}}
placeholder="Введите название проекта"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
autoFocus
/>
{error && (
<div className="mt-2 text-sm text-red-600">{error}</div>
)}
</div>
</div>
{/* Кнопка подтверждения (прибита к низу) */}
<div className="p-6 border-t border-gray-200">
<button
onClick={handleSubmit}
disabled={isSubmitting || !projectName.trim()}
className="w-full px-4 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{isSubmitting ? 'Обработка...' : 'Добавить'}
</button>
</div>
</div>
</div>
)
}
// Компонент экрана переноса проекта
function MoveProjectScreen({ project, allProjects, onClose, onSuccess }) {
@@ -241,7 +339,7 @@ function DroppableSlot({ containerId, isEmpty, maxItems, currentCount }) {
}
// Компонент для слота приоритета
function PrioritySlot({ title, projects, allProjects, onMenuClick, maxItems = null, containerId }) {
function PrioritySlot({ title, projects, allProjects, onMenuClick, maxItems = null, containerId, onAddClick }) {
return (
<div className="mb-6">
<div className="text-sm font-semibold text-gray-600 mb-2">{title}</div>
@@ -258,6 +356,14 @@ function PrioritySlot({ title, projects, allProjects, onMenuClick, maxItems = nu
onMenuClick={onMenuClick}
/>
))}
{onAddClick && containerId === 'low' && (
<button
onClick={onAddClick}
className="w-full bg-white rounded-lg p-3 border-2 border-gray-200 shadow-sm hover:shadow-md transition-all duration-200 hover:border-indigo-400 text-gray-600 hover:text-indigo-600 font-semibold"
>
+ Добавить
</button>
)}
</div>
</div>
)
@@ -289,6 +395,7 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
const [activeId, setActiveId] = useState(null)
const [selectedProject, setSelectedProject] = useState(null) // Для модального окна
const [showMoveScreen, setShowMoveScreen] = useState(false) // Для экрана переноса
const [showAddScreen, setShowAddScreen] = useState(false) // Для экрана добавления
const scrollContainerRef = useRef(null)
@@ -829,6 +936,7 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
allProjects={allProjects}
onMenuClick={handleMenuClick}
containerId="low"
onAddClick={() => setShowAddScreen(true)}
/>
</SortableContext>
@@ -905,6 +1013,17 @@ function ProjectPriorityManager({ allProjectsData, currentWeekData, shouldLoad,
}}
/>
)}
{/* Экран добавления проекта */}
{showAddScreen && (
<AddProjectScreen
onClose={() => setShowAddScreen(false)}
onSuccess={() => {
setShowAddScreen(false)
fetchProjects()
}}
/>
)}
</div>
)
}