diff --git a/.replit b/.replit index 2202770..a572720 100644 --- a/.replit +++ b/.replit @@ -18,10 +18,6 @@ externalPort = 80 localPort = 35345 externalPort = 3002 -[[ports]] -localPort = 36697 -externalPort = 4200 - [[ports]] localPort = 39063 externalPort = 3001 diff --git a/client/src/App.tsx b/client/src/App.tsx index 58da0f1..4fa2e61 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -348,7 +348,7 @@ function App() { ); case 'settings': - return ; + return setCurrentTab('templates')} />; default: return ( diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index ac5e256..98aecb4 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -172,15 +172,31 @@ "english": "Englisch (English)", "german": "Deutsch" }, - "appearance": { - "title": "Erscheinungsbild", - "description": "Passe das Aussehen an", - "themeToggle": "Thema-Umschalter ist in der Kopfzeile verfügbar" + "labels": { + "title": "Aufgabenlabels", + "description": "Erstellen und verwalten Sie Labels, um Ihre Aufgaben zu organisieren", + "createLabel": "Label erstellen", + "createNewLabel": "Neues Label erstellen", + "editLabel": "Label bearbeiten", + "labelNamePlaceholder": "Label-Name", + "cancel": "Abbrechen", + "create": "Erstellen", + "update": "Aktualisieren", + "loading": "Labels werden geladen...", + "noLabels": "Noch keine Labels", + "noLabelsDescription": "Erstellen Sie Ihr erstes Label, um Ihre Aufgaben nach Farbe und Kategorie zu organisieren.", + "deleteConfirm": "Sind Sie sicher, dass Sie dieses Label löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "created": "Label erstellt", + "createdDescription": "Ihr Label wurde erfolgreich erstellt.", + "updated": "Label aktualisiert", + "updatedDescription": "Ihr Label wurde erfolgreich aktualisiert.", + "deleted": "Label gelöscht", + "deletedDescription": "Ihr Label wurde erfolgreich gelöscht." }, "templates": { "title": "Projektvorlagen", "description": "Verwende Vorlagen zum schnellen Erstellen von Projekten", - "accessInfo": "Zugriff auf Vorlagen über das Navigationsmenü" + "manageTemplates": "Vorlagen verwalten" } }, "projectTemplate": { diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index 7d9ecbe..2efb574 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -172,15 +172,31 @@ "english": "English", "german": "German (Deutsch)" }, - "appearance": { - "title": "Appearance", - "description": "Customize the look and feel", - "themeToggle": "Theme toggle available in the header" + "labels": { + "title": "Task Labels", + "description": "Create and manage labels to organize your tasks", + "createLabel": "Create Label", + "createNewLabel": "Create New Label", + "editLabel": "Edit Label", + "labelNamePlaceholder": "Label name", + "cancel": "Cancel", + "create": "Create", + "update": "Update", + "loading": "Loading labels...", + "noLabels": "No labels yet", + "noLabelsDescription": "Create your first label to organize your tasks by color and category.", + "deleteConfirm": "Are you sure you want to delete this label? This action cannot be undone.", + "created": "Label created", + "createdDescription": "Your label has been created successfully.", + "updated": "Label updated", + "updatedDescription": "Your label has been updated successfully.", + "deleted": "Label deleted", + "deletedDescription": "Your label has been deleted successfully." }, "templates": { "title": "Project Templates", "description": "Use templates to quickly create projects", - "accessInfo": "Access templates from the navigation menu" + "manageTemplates": "Manage Templates" } }, "projectTemplate": { diff --git a/client/src/pages/settings.tsx b/client/src/pages/settings.tsx index f50526c..4a723c1 100644 --- a/client/src/pages/settings.tsx +++ b/client/src/pages/settings.tsx @@ -1,10 +1,28 @@ +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useQuery, useMutation } from '@tanstack/react-query'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Globe } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Globe, Tag, Plus, Edit, Trash2, Layers } from 'lucide-react'; +import { Label } from '@shared/schema'; +import { queryClient, apiRequest } from '@/lib/queryClient'; +import { useToast } from '@/hooks/use-toast'; -export default function Settings() { +interface SettingsProps { + onNavigateToTemplates: () => void; +} + +export default function Settings({ onNavigateToTemplates }: SettingsProps) { const { t, i18n } = useTranslation(); + const { toast } = useToast(); + + const [isLabelDialogOpen, setIsLabelDialogOpen] = useState(false); + const [editingLabel, setEditingLabel] = useState(null); + const [labelName, setLabelName] = useState(''); + const [labelColor, setLabelColor] = useState('#3B82F6'); const changeLanguage = (lng: string) => { i18n.changeLanguage(lng); @@ -12,6 +30,83 @@ export default function Settings() { console.log('Language changed to:', lng); }; + // Fetch labels + const { data: labels = [], isLoading: labelsLoading } = useQuery({ + queryKey: ['/api/labels'] + }); + + // Create label mutation + const createLabelMutation = useMutation({ + mutationFn: (data: { name: string; color: string }) => + apiRequest('POST', '/api/labels', data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/labels'] }); + setIsLabelDialogOpen(false); + setLabelName(''); + setLabelColor('#3B82F6'); + toast({ + title: t('settings.labels.created'), + description: t('settings.labels.createdDescription'), + }); + }, + }); + + // Update label mutation + const updateLabelMutation = useMutation({ + mutationFn: ({ id, ...data }: { id: string; name: string; color: string }) => + apiRequest('PATCH', `/api/labels/${id}`, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/labels'] }); + setIsLabelDialogOpen(false); + setEditingLabel(null); + setLabelName(''); + setLabelColor('#3B82F6'); + toast({ + title: t('settings.labels.updated'), + description: t('settings.labels.updatedDescription'), + }); + }, + }); + + // Delete label mutation + const deleteLabelMutation = useMutation({ + mutationFn: (id: string) => apiRequest('DELETE', `/api/labels/${id}`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/api/labels'] }); + toast({ + title: t('settings.labels.deleted'), + description: t('settings.labels.deletedDescription'), + }); + }, + }); + + const handleSaveLabel = () => { + if (!labelName.trim()) return; + + if (editingLabel) { + updateLabelMutation.mutate({ + id: editingLabel.id, + name: labelName, + color: labelColor, + }); + } else { + createLabelMutation.mutate({ name: labelName, color: labelColor }); + } + }; + + const handleEditLabel = (label: Label) => { + setEditingLabel(label); + setLabelName(label.name); + setLabelColor(label.color); + setIsLabelDialogOpen(true); + }; + + const handleDeleteLabel = (id: string) => { + if (confirm(t('settings.labels.deleteConfirm'))) { + deleteLabelMutation.mutate(id); + } + }; + return ( @@ -48,33 +143,175 @@ export default function Settings() { - {/* Appearance Settings */} - + {/* Labels Management */} + - {t('settings.appearance.title')} - - {t('settings.appearance.description')} - + + + + + {t('settings.labels.title')} + + + {t('settings.labels.description')} + + + + + + + {t('settings.labels.createLabel')} + + + + + + {editingLabel ? t('settings.labels.editLabel') : t('settings.labels.createNewLabel')} + + + + + setLabelName(e.target.value)} + data-testid="input-label-name" + /> + + + + setLabelColor(e.target.value)} + className="w-12 h-8 rounded border cursor-pointer" + data-testid="input-label-color" + /> + setLabelColor(e.target.value)} + placeholder="#3B82F6" + className="flex-1" + data-testid="input-label-color-text" + /> + + + + { + setIsLabelDialogOpen(false); + setEditingLabel(null); + setLabelName(''); + setLabelColor('#3B82F6'); + }} + className="flex-1" + data-testid="button-cancel-label" + > + {t('settings.labels.cancel')} + + + {editingLabel ? t('settings.labels.update') : t('settings.labels.create')} + + + + + + - - {t('settings.appearance.themeToggle')} - + {labelsLoading ? ( + + {t('settings.labels.loading')} + + ) : ( + + {labels.map((label) => ( + + + + + + {label.name} + + + + handleEditLabel(label)} + className="w-6 h-6" + data-testid={`button-edit-${label.id}`} + > + + + handleDeleteLabel(label.id)} + className="w-6 h-6 text-destructive hover:text-destructive" + data-testid={`button-delete-${label.id}`} + > + + + + + + ))} + {labels.length === 0 && ( + + + {t('settings.labels.noLabels')} + + {t('settings.labels.noLabelsDescription')} + + setIsLabelDialogOpen(true)} + data-testid="button-create-first-label" + > + + {t('settings.labels.createLabel')} + + + )} + + )} {/* Project Templates */} - + - {t('settings.templates.title')} + + + {t('settings.templates.title')} + {t('settings.templates.description')} - - {t('settings.templates.accessInfo')} - + + + {t('settings.templates.manageTemplates')} +
- {t('settings.appearance.themeToggle')} -
+ {t('settings.labels.noLabelsDescription')} +
- {t('settings.templates.accessInfo')} -