diff --git a/new-frontend/src/App.tsx b/new-frontend/src/App.tsx index c92cccd..c73a424 100644 --- a/new-frontend/src/App.tsx +++ b/new-frontend/src/App.tsx @@ -1,45 +1,49 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { AuthProvider } from './contexts/AuthContext' +import { TimerProvider } from './contexts/TimerContext' import { LandingPage } from './pages/LandingPage' import { AuthPage } from './pages/AuthPage' import { ProtectedRoute } from './components/layout/ProtectedRoute' import { AppShell } from './components/layout/AppShell' import { DashboardPage } from './pages/DashboardPage' import { OrganizationsPage } from './pages/OrganizationsPage' +import { TasksPage } from './pages/TasksPage' export function App() { return ( - - {/* Rotas Públicas */} - } /> - } /> - } /> + + + {/* Rotas Públicas */} + } /> + } /> + } /> - {/* Rotas Protegidas dentro do AppShell */} - - - - } - > - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + {/* Rotas Protegidas dentro do AppShell */} + + + + } + > + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + - {/* Fallback */} - } /> - + {/* Fallback */} + } /> + + ) diff --git a/new-frontend/src/components/layout/Header.tsx b/new-frontend/src/components/layout/Header.tsx index 8b11bf8..5b9a6aa 100644 --- a/new-frontend/src/components/layout/Header.tsx +++ b/new-frontend/src/components/layout/Header.tsx @@ -22,6 +22,9 @@ import { Check, } from 'lucide-react' import { useAuth } from '../../contexts/AuthContext' +import { useTimer } from '../../contexts/TimerContext' +import { formatSecondsToTimer } from '../../lib/timeUtils' +import { Square } from 'lucide-react' interface HeaderProps { onToggleMobileSidebar: () => void @@ -29,6 +32,7 @@ interface HeaderProps { export const Header = ({ onToggleMobileSidebar }: HeaderProps) => { const { user, activeOrg, setActiveOrg, logout } = useAuth() + const { activeTask, activeSeconds, stopTimer } = useTimer() const [showOrgDropdown, setShowOrgDropdown] = useState(false) const [showUserDropdown, setShowUserDropdown] = useState(false) @@ -170,24 +174,39 @@ export const Header = ({ onToggleMobileSidebar }: HeaderProps) => { {/* Right Area: Active Tracker Pill + Action Button + User Profile */}
{/* Active Timer Pill Banner */} -
-
- - - Modo Foco Ativo - - - v3.0 - -
+ {activeTask ? ( +
+
+ + + {formatSecondsToTimer(activeSeconds)} + + + {activeTask.title} + + +
+ ) : ( +
+ + Nenhuma tarefa em execução +
+ )} {/* Create Task Button */} {/* User Profile Menu Dropdown */} diff --git a/new-frontend/src/components/tasks/CreateTaskModal.tsx b/new-frontend/src/components/tasks/CreateTaskModal.tsx new file mode 100644 index 0000000..bb63d30 --- /dev/null +++ b/new-frontend/src/components/tasks/CreateTaskModal.tsx @@ -0,0 +1,405 @@ +import React, { useState, useEffect } from 'react' +import { X, Folder, Tag, User, Loader2, Sparkles } from 'lucide-react' +import type { Task, TaskStatus, Project, Category } from '../../types/task' +import type { OrganizationMember } from '../../services/organizationService' +import { projectService } from '../../services/projectService' +import { categoryService } from '../../services/categoryService' +import { organizationService } from '../../services/organizationService' + +interface CreateTaskModalProps { + isOpen: boolean + onClose: () => void + onSubmit: (taskData: { + title: string + description: string + status: TaskStatus + projectId?: number + categoryId?: number + assigneeUserId?: number + }) => Promise + organizationId: number + initialStatus?: TaskStatus + initialTask?: Task | null +} + +export const CreateTaskModal: React.FC = ({ + isOpen, + onClose, + onSubmit, + organizationId, + initialStatus = 'NOT_STARTED', + initialTask = null, +}) => { + const [title, setTitle] = useState('') + const [description, setDescription] = useState('') + const [status, setStatus] = useState(initialStatus) + const [projectId, setProjectId] = useState(undefined) + const [categoryId, setCategoryId] = useState(undefined) + const [assigneeUserId, setAssigneeUserId] = useState(undefined) + + const [projects, setProjects] = useState([]) + const [categories, setCategories] = useState([]) + const [members, setMembers] = useState([]) + + const [loading, setLoading] = useState(false) + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + + // Quick inline creation states + const [newProjectName, setNewProjectName] = useState('') + const [showAddProject, setShowAddProject] = useState(false) + const [newCategoryName, setNewCategoryName] = useState('') + const [showAddCategory, setShowAddCategory] = useState(false) + + // Populate data when modal opens + useEffect(() => { + if (!isOpen) return + + if (initialTask) { + setTitle(initialTask.title) + setDescription(initialTask.description || '') + setStatus(initialTask.status) + setProjectId(initialTask.project?.id) + setCategoryId(initialTask.category?.id) + setAssigneeUserId(initialTask.user?.id) + } else { + setTitle('') + setDescription('') + setStatus(initialStatus) + setProjectId(undefined) + setCategoryId(undefined) + setAssigneeUserId(undefined) + } + setError(null) + setShowAddProject(false) + setShowAddCategory(false) + + // Load projects, categories, members + const loadMetadata = async () => { + setLoading(true) + try { + const [projRes, catRes, memRes] = await Promise.allSettled([ + projectService.listProjects(organizationId), + categoryService.listCategories(organizationId), + organizationService.listMembers(organizationId), + ]) + + if (projRes.status === 'fulfilled') setProjects(projRes.value || []) + if (catRes.status === 'fulfilled') setCategories(catRes.value || []) + if (memRes.status === 'fulfilled') setMembers(memRes.value || []) + } catch (err) { + console.error('Error fetching metadata', err) + } finally { + setLoading(false) + } + } + + loadMetadata() + }, [isOpen, organizationId, initialStatus, initialTask]) + + if (!isOpen) return null + + const handleCreateProject = async (e: React.FormEvent) => { + e.preventDefault() + if (!newProjectName.trim()) return + try { + const created = await projectService.createProject(organizationId, { + name: newProjectName.trim(), + color: '#0f766e', + }) + setProjects((prev) => [...prev, created]) + setProjectId(created.id) + setNewProjectName('') + setShowAddProject(false) + } catch (err: unknown) { + console.error(err) + setError('Falha ao criar projeto.') + } + } + + const handleCreateCategory = async (e: React.FormEvent) => { + e.preventDefault() + if (!newCategoryName.trim()) return + try { + const created = await categoryService.createCategory(organizationId, { + name: newCategoryName.trim(), + color: '#d97706', + }) + setCategories((prev) => [...prev, created]) + setCategoryId(created.id) + setNewCategoryName('') + setShowAddCategory(false) + } catch (err: unknown) { + console.error(err) + setError('Falha ao criar categoria.') + } + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!title.trim()) { + setError('O título da tarefa é obrigatório.') + return + } + + setSubmitting(true) + setError(null) + + try { + await onSubmit({ + title: title.trim(), + description: description.trim(), + status, + projectId, + categoryId, + assigneeUserId, + }) + onClose() + } catch (err: unknown) { + console.error(err) + setError(err instanceof Error ? err.message : 'Erro ao salvar tarefa.') + } finally { + setSubmitting(false) + } + } + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal Dialog */} +
+ {/* Header */} +
+
+
+ +
+
+

+ {initialTask ? 'Editar Tarefa' : 'Nova Tarefa'} +

+

+ {initialTask + ? 'Atualize as informações da demanda.' + : 'Preencha os campos para cadastrar no quadro de atividades.'} +

+
+
+ +
+ + {/* Body Form */} +
+ {error && ( +
+ {error} +
+ )} + + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="Ex: Implementar autenticação via OAuth2..." + className="w-full px-3.5 py-2.5 rounded-xl border border-border bg-surface text-sm text-foreground placeholder-muted/60 focus:outline-none focus:ring-2 focus:ring-brand/40 focus:border-brand transition-all" + /> +
+ + {/* Description */} +
+ +