import React, { useState, useEffect, useCallback } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { Plus, RefreshCw, RotateCcw, BarChart3, Layers, LayoutDashboard, AlertCircle, Check, User, Users, } from 'lucide-react' import { useAuth } from '../contexts/AuthContext' import { useTimer } from '../contexts/TimerContext' import { taskService } from '../services/taskService' import { projectService } from '../services/projectService' import { categoryService } from '../services/categoryService' import type { Task, Project, Category } from '../types/task' import { getLocalPreferences, syncPreferencesFromBackend, savePreferencesToBackend, DEFAULT_V2_CARDS, type DashboardV2Card, } from '../lib/userPreferences' import { getUserWorkGoal, type WorkGoal } from '../lib/userGoals' import { BiUniformCard } from '../components/dashboard/BiUniformCard' import { CreateBiCardModal } from '../components/dashboard/CreateBiCardModal' export const DashboardV2Page: React.FC = () => { const { user, activeOrg } = useAuth() const { activeTask, activeSeconds, startTimer, stopTimer, syncWithTasks } = useTimer() const [tasks, setTasks] = useState([]) const [projects, setProjects] = useState([]) const [categories, setCategories] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [error, setError] = useState(null) // Work goal const [workGoal, setWorkGoal] = useState(() => getUserWorkGoal(user?.email)) // Dashboard v2 BI Cards State const [biCards, setBiCards] = useState(() => getLocalPreferences(user?.email).dashboardV2?.cards || DEFAULT_V2_CARDS ) // Modal State const [isModalOpen, setIsModalOpen] = useState(false) const [editingCard, setEditingCard] = useState(null) const [saveToast, setSaveToast] = useState(false) // Scope Toggle State (Individual vs Equipe) const [searchParams, setSearchParams] = useSearchParams() const initialScope = (searchParams.get('scope') as 'mine' | 'team') || (localStorage.getItem('beetask_bi_scope') as 'mine' | 'team') || 'team' const [viewScope, setViewScope] = useState<'mine' | 'team'>(initialScope) const handleScopeChange = (newScope: 'mine' | 'team') => { setViewScope(newScope) try { localStorage.setItem('beetask_bi_scope', newScope) } catch (_) {} setSearchParams( (prev) => { const next = new URLSearchParams(prev) next.set('scope', newScope) return next }, { replace: true } ) } const activeOrgId = activeOrg?.id // Load backend data const loadData = useCallback(async () => { if (!activeOrgId) return try { setRefreshing(true) const [tasksRes, projsRes, catsRes] = await Promise.allSettled([ taskService.listTasks({ organizationId: activeOrgId }), projectService.listProjects(activeOrgId), categoryService.listCategories(activeOrgId), ]) const loadedTasks = tasksRes.status === 'fulfilled' ? tasksRes.value || [] : [] setTasks(loadedTasks) syncWithTasks(loadedTasks) if (projsRes.status === 'fulfilled') { setProjects(projsRes.value || []) } if (catsRes.status === 'fulfilled') { setCategories(catsRes.value || []) } } catch (err) { console.error(err) setError('Falha ao carregar dados do workspace.') } finally { setLoading(false) setRefreshing(false) } }, [activeOrgId, syncWithTasks]) useEffect(() => { loadData() }, [loadData]) // Sync preferences on load & user change useEffect(() => { if (user?.email) { setWorkGoal(getUserWorkGoal(user.email)) setBiCards(getLocalPreferences(user.email).dashboardV2?.cards || DEFAULT_V2_CARDS) syncPreferencesFromBackend(user.email).then((prefs) => { if (prefs?.dashboardV2?.cards) { setBiCards(prefs.dashboardV2.cards) } }) } }, [user?.email]) // Persist helper const persistBiCards = useCallback( async (newCards: DashboardV2Card[]) => { if (!user?.email) return setBiCards(newCards) try { await savePreferencesToBackend(user.email, { dashboardV2: { cards: newCards }, workGoal, }) setSaveToast(true) setTimeout(() => setSaveToast(false), 1500) } catch (err) { console.error('Failed to save BI cards to backend:', err) } }, [user?.email, workGoal] ) // Add or Edit Card const handleSaveCard = (card: DashboardV2Card) => { const exists = biCards.some((c) => c.id === card.id) let next: DashboardV2Card[] if (exists) { next = biCards.map((c) => (c.id === card.id ? card : c)) } else { next = [...biCards, card] } persistBiCards(next) } // Delete Card const handleDeleteCard = (cardId: string) => { const next = biCards.filter((c) => c.id !== cardId) persistBiCards(next) } // Drag and Drop reordering state & handlers const [draggedCardId, setDraggedCardId] = useState(null) const [dragOverCardId, setDragOverCardId] = useState(null) const handleSwapCards = (idA: string, idB: string) => { if (idA === idB) return const indexA = biCards.findIndex((c) => c.id === idA) const indexB = biCards.findIndex((c) => c.id === idB) if (indexA === -1 || indexB === -1) return const next = [...biCards] const temp = next[indexA] next[indexA] = next[indexB] next[indexB] = temp persistBiCards(next) } const handleDragStart = (e: React.DragEvent, id: string) => { e.dataTransfer.setData('text/plain', id) e.dataTransfer.effectAllowed = 'move' setDraggedCardId(id) } const handleDragOver = (e: React.DragEvent, id: string) => { e.preventDefault() e.dataTransfer.dropEffect = 'move' if (dragOverCardId !== id) { setDragOverCardId(id) } } const handleDragLeave = (_e: React.DragEvent, id: string) => { if (dragOverCardId === id) { setDragOverCardId(null) } } const handleDrop = (e: React.DragEvent, targetId: string) => { e.preventDefault() const sourceId = (e.dataTransfer.getData('text/plain') as string) || draggedCardId if (sourceId && sourceId !== targetId) { handleSwapCards(sourceId, targetId) } setDraggedCardId(null) setDragOverCardId(null) } const handleDragEnd = () => { setDraggedCardId(null) setDragOverCardId(null) } // Move Card Left/Right const handleMoveCard = (cardId: string, direction: 'prev' | 'next') => { const currentIdx = biCards.findIndex((c) => c.id === cardId) if (currentIdx === -1) return const targetIdx = direction === 'prev' ? currentIdx - 1 : currentIdx + 1 if (targetIdx < 0 || targetIdx >= biCards.length) return const next = [...biCards] const temp = next[currentIdx] next[currentIdx] = next[targetIdx] next[targetIdx] = temp persistBiCards(next) } // Reset to default cards const handleResetDefault = () => { persistBiCards(DEFAULT_V2_CARDS) } // Clear all cards to start 100% from scratch const handleClearAll = () => { persistBiCards([]) } // Timer handler const handleToggleTimer = async (task: Task) => { try { if (activeTask?.id === task.id) { const stopped = await stopTimer(task) if (stopped) { setTasks((prev) => prev.map((t) => (t.id === stopped.id ? stopped : t))) } } else { const started = await startTimer(task) setTasks((prev) => prev.map((t) => (t.id === started.id ? started : t))) } } catch (err) { console.error(err) setError('Erro ao acionar cronômetro.') } } const firstName = user?.name ? user.name.split(' ')[0] : 'Desenvolvedor' return (
{/* Top Banner with Navigation Tabs */}
{/* Tabs: Visão Geral vs BI v2 */}
Visão Geral
Painel BI v2 (Modular)

BI Customizável, {firstName}!{' '} Monte seu painel.

Crie e combine indicadores do zero em cards uniformes. Filtre por projeto, período e status como em um BI corporativo.

{/* Action Controls */}
{biCards.length > 0 && ( )}
{/* Save Toast Feedback */} {saveToast && (
Configuração salva no banco de dados!
)} {error && (
{error}
)} {/* Scope Selector: Individual vs Equipe */}
Escopo do Painel:
{viewScope === 'mine' ? 'Exibindo cards com dados das suas tarefas atribuídas' : 'Exibindo cards com métricas consolidadas de todo o workspace'}
{/* Uniform Modular Grid */} {loading ? (
{[1, 2, 3, 4, 5, 6].map((i) => (
))}
) : biCards.length === 0 ? (

Seu Painel BI está vazio

Você pode criar cards totalmente customizados do zero escolhendo métricas, filtros de projeto, períodos e status.

) : (
{biCards.map((card, idx) => ( { setEditingCard(c) setIsModalOpen(true) }} onDeleteCard={handleDeleteCard} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} onDragEnd={handleDragEnd} /> ))}
)} {/* Create / Edit BI Card Modal */} { setIsModalOpen(false) setEditingCard(null) }} onSave={handleSaveCard} editingCard={editingCard} projects={projects} categories={categories} />
) }