feat: добавлена возможность создания проектов через UI - версия 2.7.0
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 38s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 38s
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user