import { useCallback, useEffect, useMemo, useState } from 'react' import type { StoryGroup } from './types/jira' import { fetchAllIssuesByJql, MIGRATION_EPIC_KEY, MIGRATION_JQL, jiraClient } from './api/jiraClient' import { groupSubtasksUnderStories } from './lib/groupIssues' import { statusToPhase } from './lib/statusPhase' import { appendBurnupSnapshot, loadBurnupHistory, type BurnupPoint } from './lib/burnupHistory' import { loadDashboardConfig, saveDashboardConfig, type DashboardConfig, } from './lib/dashboardConfig' import { assigneeMatchesMyView } from './lib/assigneeMatch' import { isAxiosError } from 'axios' import { ExecutiveSummary } from './components/ExecutiveSummary' import { BurnupChart } from './components/BurnupChart' import { StoryCard } from './components/StoryCard' import { BoardView } from './components/BoardView' import { DashboardSkeleton } from './components/DashboardSkeleton' import { MilestonesTimeline } from './components/MilestonesTimeline' import { DashboardSettingsModal } from './components/DashboardSettingsModal' type ViewMode = 'list' | 'board' export default function App() { const [groups, setGroups] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [updatedAt, setUpdatedAt] = useState(null) const [view, setView] = useState('list') const [burnupData, setBurnupData] = useState(() => loadBurnupHistory()) const [dashboardCfg, setDashboardCfg] = useState(() => loadDashboardConfig()) const [settingsOpen, setSettingsOpen] = useState(false) const myViewActive = Boolean(dashboardCfg.myViewActive) const displayGroups = useMemo(() => { if (!myViewActive) return groups return groups.filter((g) => g.subtasks.some((st) => assigneeMatchesMyView(st, dashboardCfg)), ) }, [groups, myViewActive, dashboardCfg]) const toggleMyView = () => { const next: DashboardConfig = { ...dashboardCfg, myViewActive: !myViewActive } setDashboardCfg(next) saveDashboardConfig(next) } const saveSettings = (next: DashboardConfig) => { setDashboardCfg(next) saveDashboardConfig(next) } const load = useCallback(async (signal?: AbortSignal) => { setLoading(true) setError(null) try { const issues = await fetchAllIssuesByJql(MIGRATION_JQL, signal) const grouped = groupSubtasksUnderStories(issues) setGroups(grouped) setUpdatedAt(new Date()) const totalSubs = grouped.reduce((acc, g) => acc + g.subtasks.length, 0) const doneSubs = grouped.reduce( (acc, g) => acc + g.subtasks.filter((s) => statusToPhase(s.fields.status.name) === 'done').length, 0, ) setBurnupData(appendBurnupSnapshot(doneSubs, totalSubs)) } catch (e) { if (signal?.aborted || (isAxiosError(e) && e.code === 'ERR_CANCELED')) { return } if (isAxiosError(e)) { const msg = typeof e.response?.data === 'object' && e.response.data !== null && 'errorMessages' in e.response.data && Array.isArray((e.response.data as { errorMessages: string[] }).errorMessages) ? (e.response.data as { errorMessages: string[] }).errorMessages.join(' ') : e.message setError(msg || 'Erreur réseau Jira') } else { setError(e instanceof Error ? e.message : 'Erreur inconnue') } setGroups([]) } finally { if (!signal?.aborted) setLoading(false) } }, []) useEffect(() => { const ac = new AbortController() void load(ac.signal) return () => ac.abort() }, [load]) const baseOk = import.meta.env.DEV ? true : Boolean(jiraClient.defaults.baseURL) return (

OroCommerce · Migration exécutive

Pilotage migration

Périmètre DCC sous l’épopée{' '} {MIGRATION_EPIC_KEY} (JQL{' '} parentEpic, sous-tâches incluses), parent résolu (clé ou id), jalons, export JSON pour Synology.

{updatedAt && !loading && ( Mis à jour {updatedAt.toLocaleTimeString('fr-FR')} )}
{!baseOk && (
Configurez VITE_JIRA_BASE_URL pour un build de production pointant vers votre proxy HTTPS (le proxy Vite ne s’applique qu’en npm run dev).
)} {error && (
{error}
)} {loading && } {!loading && !error && groups.length === 0 && (

Aucun ticket ne correspond au JQL actuel.

)} {!loading && !error && groups.length > 0 && ( <> setSettingsOpen(true)} />

Burnup

{view === 'list' ? 'Stories (liste)' : 'Stories par composant'}

{myViewActive && ( Filtre actif : {displayGroups.length} / {groups.length} stories )}
{displayGroups.length === 0 ? (

Aucune story ne correspond à « Ma vue ». Vérifiez vos assignations ou configurez votre accountId / e-mail dans les réglages.

) : view === 'list' ? (
{displayGroups.map((g) => ( ))}
) : ( )}
)}
setSettingsOpen(false)} onSave={saveSettings} />
) }