Files
brainwave/frontend/src/components/Workspace.tsx
T
LaurensBartelds acbcfde617
Test / backend (push) Successful in 13s
Test / frontend (push) Successful in 24s
feat: add storage management features including user storage quota and usage tracking
- Updated database schema to include `storage_quota_bytes` and `plan` for users.
- Implemented storage usage calculations in `storageRepo` and added tests for storage functionality.
- Created `StorageManager` component to display and manage user storage.
- Enhanced user settings to show storage details and warnings for high usage.
- Added translations for new storage-related strings in English and Dutch.
- Updated styles for storage overview and details in CSS.
2026-04-27 15:29:43 +02:00

95 lines
3.3 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react'
import { useNavigate, useParams, Navigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useAuth } from '../contexts/AuthContext'
import * as examApi from '../api/examApi'
import * as resultApi from '../api/resultApi'
import type { Exam } from '../types'
import MyExams from './MyExams'
import MyResults from './MyResults'
import Settings from './Settings'
import StorageManager from './StorageManager'
type TabKey = 'exams' | 'results' | 'storage' | 'settings'
const TAB_KEYS: { key: TabKey; labelKey: string; icon: string }[] = [
{ key: 'exams', labelKey: 'workspace.myExams', icon: '◇' },
{ key: 'results', labelKey: 'workspace.results', icon: '✦' },
{ key: 'storage', labelKey: 'workspace.storage', icon: '◉' },
{ key: 'settings', labelKey: 'workspace.settings', icon: '⚙' },
]
interface Props {
onLoadExam: (exam: Exam, saveToWorkspace?: boolean, examId?: number | null) => void
}
export default function Workspace({ onLoadExam }: Props) {
const { t } = useTranslation()
const { user, loading: authLoading } = useAuth()
const navigate = useNavigate()
const { tab } = useParams<{ tab: string }>()
const activeTab = (tab as TabKey) ?? 'exams'
const [examCount, setExamCount] = useState(0)
const [resultCount, setResultCount] = useState(0)
const loadCounts = useCallback(async () => {
try {
const exams = await examApi.listExams()
setExamCount(exams.length)
const results = await resultApi.listResults()
setResultCount(results.total)
} catch {
// silently ignore
}
}, [])
useEffect(() => {
if (user) void loadCounts()
}, [user, loadCounts])
if (authLoading) return null
if (!user) return <Navigate to="/" replace />
const setTab = (key: TabKey) => navigate(`/workspace/${key}`)
return (
<div className="workspace-shell">
<aside className="workspace-sidebar">
<div className="workspace-profile">
<div className="workspace-avatar">
{user.displayName.charAt(0).toUpperCase()}
</div>
<div className="workspace-name">{user.displayName}</div>
<div className="workspace-email">{user.email}</div>
</div>
<nav className="workspace-nav">
{TAB_KEYS.map((tabItem) => (
<button
key={tabItem.key}
className={`workspace-nav-item${activeTab === tabItem.key ? ' active' : ''}`}
onClick={() => setTab(tabItem.key)}
>
<span className="workspace-nav-icon">{tabItem.icon}</span>
<span className="workspace-nav-label">{t(tabItem.labelKey)}</span>
{tabItem.key === 'exams' && examCount > 0 && (
<span className="workspace-nav-badge">{examCount}</span>
)}
{tabItem.key === 'results' && resultCount > 0 && (
<span className="workspace-nav-badge">{resultCount}</span>
)}
</button>
))}
</nav>
</aside>
<main className="workspace-main">
{activeTab === 'exams' && <MyExams onLoadExam={onLoadExam} />}
{activeTab === 'results' && <MyResults onLoadExam={onLoadExam} />}
{activeTab === 'storage' && <StorageManager />}
{activeTab === 'settings' && <Settings />}
</main>
</div>
)
}