"use client"; import { useState, useCallback, useRef } from "react"; import { useTranslations } from "next-intl"; import { Plus, Pencil, Trash2, ExternalLink, PanelRight, GripVertical } from "lucide-react"; import { icons as lucideIcons, type LucideIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { SettingsSection, SettingItem, ToggleSwitch } from "./settings-section"; import { IconPicker } from "@/components/layout/icon-picker"; import { useSettingsStore, type SidebarApp } from "@/stores/settings-store"; import { useConfirmDialog } from "@/hooks/use-confirm-dialog"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { cn, generateUUID } from "@/lib/utils"; interface SidebarAppFormData { name: string; url: string; icon: string; openMode: "tab" | "inline"; showOnMobile: boolean; } function AppForm({ app, onSave, onCancel, }: { app?: SidebarApp; onSave: (data: SidebarAppFormData) => void; onCancel: () => void; }) { const t = useTranslations("sidebar_apps"); const isEditing = !!app; const [formData, setFormData] = useState({ name: app?.name || "", url: app?.url || "", icon: app?.icon || "Globe", openMode: app?.openMode || "tab", showOnMobile: app?.showOnMobile ?? false, }); const [errors, setErrors] = useState>({}); const validate = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = t("name_required"); } if (!formData.url.trim()) { newErrors.url = t("url_required"); } else { try { const parsed = new URL(formData.url); if (!["http:", "https:"].includes(parsed.protocol)) { newErrors.url = t("url_invalid"); } } catch { newErrors.url = t("url_invalid"); } } if (!formData.icon) { newErrors.icon = t("icon_required"); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!validate()) return; onSave(formData); }; const SelectedIcon = formData.icon ? (lucideIcons[formData.icon as keyof typeof lucideIcons] as LucideIcon | undefined) : null; return (
setFormData({ ...formData, name: e.target.value })} placeholder={t("name_placeholder")} className="mt-1" /> {errors.name &&

{errors.name}

}
setFormData({ ...formData, url: e.target.value })} placeholder="https://example.com" className="mt-1" /> {errors.url &&

{errors.url}

}
{SelectedIcon && (
)} {formData.icon}
setFormData({ ...formData, icon })} /> {errors.icon &&

{errors.icon}

}
); } export function SidebarAppsSettings() { const t = useTranslations("settings.sidebar_apps"); const tApps = useTranslations("sidebar_apps"); const { sidebarApps, keepAppsLoaded, addSidebarApp, updateSidebarApp, removeSidebarApp, reorderSidebarApps, updateSetting } = useSettingsStore(); const [editingApp, setEditingApp] = useState(null); const [showAddForm, setShowAddForm] = useState(false); const { dialogProps: confirmDialogProps, confirm: confirmDialog } = useConfirmDialog(); const [dragOverIndex, setDragOverIndex] = useState(null); const draggedIndexRef = useRef(null); const handleAdd = useCallback((data: SidebarAppFormData) => { const id = `app-${generateUUID()}`; addSidebarApp({ id, ...data }); setShowAddForm(false); }, [addSidebarApp]); const handleUpdate = useCallback((id: string, data: SidebarAppFormData) => { updateSidebarApp(id, data); setEditingApp(null); }, [updateSidebarApp]); const handleDelete = useCallback(async (app: SidebarApp) => { const confirmed = await confirmDialog({ title: tApps("delete_confirm_title"), message: tApps("delete_confirm", { name: app.name }), confirmText: tApps("delete"), variant: 'destructive', }); if (!confirmed) return; removeSidebarApp(app.id); }, [confirmDialog, tApps, removeSidebarApp]); const handleDragStart = useCallback((e: React.DragEvent, index: number) => { draggedIndexRef.current = index; e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", String(index)); }, []); const handleDragOver = useCallback((e: React.DragEvent, index: number) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; setDragOverIndex(index); }, []); const handleDrop = useCallback((e: React.DragEvent, dropIndex: number) => { e.preventDefault(); setDragOverIndex(null); const fromIndex = draggedIndexRef.current; if (fromIndex === null || fromIndex === dropIndex) return; const newApps = [...sidebarApps]; const [moved] = newApps.splice(fromIndex, 1); newApps.splice(dropIndex, 0, moved); reorderSidebarApps(newApps); }, [sidebarApps, reorderSidebarApps]); const handleDragEnd = useCallback(() => { draggedIndexRef.current = null; setDragOverIndex(null); }, []); return ( <> updateSetting("keepAppsLoaded", v)} />
{sidebarApps.length === 0 && !showAddForm && (

{tApps("no_apps_hint")}

)} {sidebarApps.map((app, index) => { if (editingApp === app.id) { return ( handleUpdate(app.id, data)} onCancel={() => setEditingApp(null)} /> ); } const AppIcon = lucideIcons[app.icon as keyof typeof lucideIcons] as LucideIcon | undefined; return (
handleDragStart(e, index)} onDragOver={(e) => handleDragOver(e, index)} onDrop={(e) => handleDrop(e, index)} onDragEnd={handleDragEnd} className={cn( "flex items-center gap-3 p-3 border rounded-lg transition-colors", dragOverIndex === index ? "border-primary bg-primary/5" : "border-border hover:bg-muted/50" )} >
{AppIcon ? : null}
{app.name}
{app.url}
{app.openMode === "inline" ? tApps("inline_badge") : tApps("tab_badge")}
); })} {showAddForm && ( setShowAddForm(false)} /> )} {!showAddForm && !editingApp && ( )}
); }