fix(keep-notes): sidebar chevron, labels sync, batch org errors, perf guards

- Notebooks: chevron visible when expanded (remove overflow clip), functional expand state
- Labels: sync/cleanup by notebookId, reconcile after note move
- Settings: refresh notebooks after cleanup; label dialog routing
- ConnectionsBadge lazy-load; reminder check persistence; i18n keys

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-04-13 22:07:09 +02:00
parent fa7e166f3e
commit 39671c6472
16 changed files with 469 additions and 303 deletions

View File

@@ -11,19 +11,27 @@ import {
DialogTitle,
DialogTrigger,
} from './ui/dialog'
import { Badge } from './ui/badge'
import { Settings, Plus, Palette, Trash2, Tag } from 'lucide-react'
import { LABEL_COLORS, LabelColorName } from '@/lib/types'
import { cn } from '@/lib/utils'
import { useLabels } from '@/context/LabelContext'
import { useLanguage } from '@/lib/i18n'
export function LabelManagementDialog() {
export interface LabelManagementDialogProps {
/** Mode contrôlé (ex. ouverture depuis la liste des carnets) */
open?: boolean
onOpenChange?: (open: boolean) => void
}
export function LabelManagementDialog(props: LabelManagementDialogProps = {}) {
const { open, onOpenChange } = props
const { labels, loading, addLabel, updateLabel, deleteLabel } = useLabels()
const { t } = useLanguage()
const [newLabel, setNewLabel] = useState('')
const [editingColorId, setEditingColorId] = useState<string | null>(null)
const controlled = open !== undefined && onOpenChange !== undefined
const handleAddLabel = async () => {
const trimmed = newLabel.trim()
if (trimmed) {
@@ -55,13 +63,7 @@ export function LabelManagementDialog() {
}
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" title={t('labels.manage')}>
<Settings className="h-5 w-5" />
</Button>
</DialogTrigger>
const dialogContent = (
<DialogContent
className="max-w-md"
onInteractOutside={(event) => {
@@ -117,23 +119,23 @@ export function LabelManagementDialog() {
{/* List labels */}
<div className="max-h-[60vh] overflow-y-auto space-y-2">
{loading ? (
<p className="text-sm text-gray-500">{t('labels.loading')}</p>
<p className="text-sm text-muted-foreground">{t('labels.loading')}</p>
) : labels.length === 0 ? (
<p className="text-sm text-gray-500">{t('labels.noLabelsFound')}</p>
<p className="text-sm text-muted-foreground">{t('labels.noLabelsFound')}</p>
) : (
labels.map((label) => {
const colorClasses = LABEL_COLORS[label.color]
const isEditing = editingColorId === label.id
return (
<div key={label.id} className="flex items-center justify-between p-2 rounded-md hover:bg-gray-100 dark:hover:bg-zinc-800/50 group">
<div key={label.id} className="flex items-center justify-between p-2 rounded-md hover:bg-accent/50 group">
<div className="flex items-center gap-3 flex-1 relative">
<Tag className={cn("h-4 w-4", colorClasses.text)} />
<span className="font-medium text-sm">{label.name}</span>
{/* Color Picker Popover */}
{isEditing && (
<div className="absolute z-20 top-8 left-0 bg-white dark:bg-zinc-900 border rounded-lg shadow-xl p-3 animate-in fade-in zoom-in-95 w-48">
<div className="absolute z-20 top-8 left-0 bg-popover text-popover-foreground border border-border rounded-lg shadow-xl p-3 animate-in fade-in zoom-in-95 w-48">
<div className="grid grid-cols-5 gap-2">
{(Object.keys(LABEL_COLORS) as LabelColorName[]).map((color) => {
const classes = LABEL_COLORS[color]
@@ -159,7 +161,7 @@ export function LabelManagementDialog() {
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
className="h-8 w-8 text-muted-foreground hover:text-foreground"
onClick={() => setEditingColorId(isEditing ? null : label.id)}
title={t('labels.changeColor')}
>
@@ -182,6 +184,24 @@ export function LabelManagementDialog() {
</div>
</div>
</DialogContent>
)
if (controlled) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
{dialogContent}
</Dialog>
)
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" title={t('labels.manage')}>
<Settings className="h-5 w-5" />
</Button>
</DialogTrigger>
{dialogContent}
</Dialog>
)
}