feat(insights): UX gap closure — i18n, network graph, recalc panel, toasts
Bugs fixes: - network-graph.tsx: 2 strings hardcoded FR → props i18n (untitledLabel, resetFocusLabel) - Panneau recalcul: clés i18n réutilisées avec mauvais sens → nouvelles clés recalcSystem.* - 13 locales: section insightsView.* complète (67 clés × 13 = 871 traductions) UX features: - Hint /insights ≠ /graph (semanticGraphLegend + lien vers /graph) - Toasts succès/échec/zéro-clusters après analyse (analysisSuccess/Failed/NoClusters) - État vide différencié: emptyNeedMoreNotes si < 10 notes - Tips contextuels sections Bridge Notes et Suggestions Sprint: - 4 epics marqués done (3,4,5,6 — toutes stories terminées) - sprint-status.yaml last_updated corrigé
This commit is contained in:
@@ -18,7 +18,10 @@ import {
|
||||
AlertCircle,
|
||||
ChevronRight,
|
||||
Database,
|
||||
ArrowRight,
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Note {
|
||||
id: string
|
||||
@@ -199,6 +202,12 @@ export default function InsightsPage() {
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const clusterCount = data.clusters?.length || 0
|
||||
if (clusterCount === 0) {
|
||||
toast.info(t('insightsView.analysisNoClusters'))
|
||||
} else {
|
||||
toast.success(t('insightsView.analysisSuccess', { count: clusterCount }))
|
||||
}
|
||||
const clustersWithColors = (data.clusters || []).map((c: Cluster, i: number) => ({
|
||||
...c,
|
||||
id: c.clusterId.toString(),
|
||||
@@ -225,6 +234,7 @@ export default function InsightsPage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error running analysis:', error)
|
||||
toast.error(t('insightsView.analysisFailed'))
|
||||
} finally {
|
||||
setIsCalculating(false)
|
||||
}
|
||||
@@ -253,6 +263,12 @@ export default function InsightsPage() {
|
||||
<p className="text-[10px] text-concrete tracking-[0.25em] uppercase font-bold">
|
||||
{t('insightsView.subtitle')}
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5 mt-1.5 text-[10px] text-concrete">
|
||||
<span>{t('insightsView.semanticGraphLegend')}</span>
|
||||
<Link href="/graph" className="inline-flex items-center gap-0.5 text-ochre hover:underline font-medium">
|
||||
{t('insightsView.openGraphMap')} <ArrowRight size={9} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between sm:justify-end gap-3">
|
||||
@@ -324,7 +340,9 @@ export default function InsightsPage() {
|
||||
{t('insightsView.emptyTitle')}
|
||||
</h3>
|
||||
<p className="text-sm text-concrete leading-relaxed mb-6">
|
||||
{t('insightsView.emptyDescription')}
|
||||
{embeddingStats && embeddingStats.total < 10
|
||||
? t('insightsView.emptyNeedMoreNotes', { count: 10 - embeddingStats.total })
|
||||
: t('insightsView.emptyDescription')}
|
||||
</p>
|
||||
<button
|
||||
onClick={performAnalysis}
|
||||
@@ -378,6 +396,9 @@ export default function InsightsPage() {
|
||||
onNoteSelect={handleNoteClick}
|
||||
selectedClusterId={selectedClusterId}
|
||||
onClusterSelect={setSelectedClusterId}
|
||||
untitledLabel={t('insightsView.unknownNote')}
|
||||
resetFocusLabel={t('insightsView.resetFocus')}
|
||||
fitViewLabel={t('insightsView.fitGraphView')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -508,22 +529,22 @@ export default function InsightsPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Sliders size={15} className="text-ochre" />
|
||||
<h4 className="text-[11px] font-black uppercase tracking-[0.2em] text-ink dark:text-dark-ink">
|
||||
{t('insightsView.clusters.title')}
|
||||
{t('insightsView.recalcSystem.title')}
|
||||
</h4>
|
||||
</div>
|
||||
<span className="flex items-center gap-1 text-[9.5px] font-bold text-emerald-500 uppercase">
|
||||
<CheckCircle2 size={11} /> {t('insightsView.resync')}
|
||||
<CheckCircle2 size={11} /> {t('insightsView.recalcSystem.statusSynced')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 pt-1">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[9px] text-concrete block">{t('insightsView.mapping')}</span>
|
||||
<span className="text-[9px] text-concrete block">{t('insightsView.recalcSystem.scheduledCron')}</span>
|
||||
<p className="text-xs text-ink dark:text-dark-ink font-semibold flex items-center gap-1.5">
|
||||
<Clock size={12} className="opacity-50" /> 04:00
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[9px] text-concrete block">{t('insightsView.resync')}</span>
|
||||
<span className="text-[9px] text-concrete block">{t('insightsView.recalcSystem.lastSync')}</span>
|
||||
<p className="text-xs text-ink dark:text-dark-ink font-bold font-mono">
|
||||
{lastSyncTime || '—'}
|
||||
</p>
|
||||
@@ -613,6 +634,7 @@ export default function InsightsPage() {
|
||||
{t('insightsView.bridgeNotes.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-[10px] text-concrete italic px-1 -mt-2 leading-relaxed">{t('insightsView.tipBridgeNotes')}</p>
|
||||
<div className="space-y-3">
|
||||
{bridgeList.map(bridge => (
|
||||
<motion.div
|
||||
@@ -672,6 +694,7 @@ export default function InsightsPage() {
|
||||
{t('insightsView.suggestions.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-[10px] text-concrete italic px-1 -mt-2 leading-relaxed">{t('insightsView.tipSuggestions')}</p>
|
||||
<div className="space-y-4">
|
||||
{suggestions.map(s => (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user