feat(dashboard): tableau de bord Second Brain configurable avec chargement progressif
Briefing granulaire, pistes rapides puis enrichissement async, layout persisté v5, suggestions agents, intégration Gmail et navigation sidebar alignée sur /home. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
Edge,
|
||||
@@ -47,6 +48,7 @@ export function ClusterVisualization({
|
||||
bridgeNotes,
|
||||
onNodeClick
|
||||
}: ClusterVisualizationProps) {
|
||||
const { t } = useLanguage()
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
||||
const [selectedCluster, setSelectedCluster] = useState<number | null>(null)
|
||||
@@ -79,8 +81,8 @@ export function ClusterVisualization({
|
||||
data: {
|
||||
label: (
|
||||
<div className="px-3 py-1 rounded-full text-sm font-medium" style={{ backgroundColor: color }}>
|
||||
{cluster.name || `Cluster ${cluster.clusterId}`}
|
||||
<span className="ml-2 text-xs opacity-75">({cluster.noteIds.length} notes)</span>
|
||||
{cluster.name || t('clusters.clusterLabel', { id: cluster.clusterId })}
|
||||
<span className="ml-2 text-xs opacity-75">({cluster.noteIds.length} {t('clusters.notes')})</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -174,8 +176,8 @@ export function ClusterVisualization({
|
||||
<svg className="w-16 h-16 mx-auto mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
<p>No clusters to display</p>
|
||||
<p className="text-sm mt-2">Create more notes to generate clusters</p>
|
||||
<p>{t('clusters.empty')}</p>
|
||||
<p className="text-sm mt-2">{t('clusters.emptyHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -200,10 +202,10 @@ export function ClusterVisualization({
|
||||
{selectedCluster !== null && (
|
||||
<div className="absolute bottom-4 left-4 bg-white rounded-lg shadow-lg p-4 max-w-xs">
|
||||
<h3 className="font-semibold mb-2">
|
||||
{clusters.find(c => c.clusterId === selectedCluster)?.name || `Cluster ${selectedCluster}`}
|
||||
{clusters.find(c => c.clusterId === selectedCluster)?.name || t('clusters.clusterLabel', { id: selectedCluster })}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{clusters.find(c => c.clusterId === selectedCluster)?.noteIds.length || 0} notes
|
||||
{clusters.find(c => c.clusterId === selectedCluster)?.noteIds.length || 0} {t('clusters.notes')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -212,11 +214,11 @@ export function ClusterVisualization({
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded-full bg-yellow-400 border-2 border-yellow-600"></div>
|
||||
<span>Bridge note</span>
|
||||
<span>{t('clusters.bridgeNote')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 rounded-full bg-blue-500"></div>
|
||||
<span>Regular note</span>
|
||||
<span>{t('clusters.regularNote')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user