Add dashboard and navigation translations with date-fns locale support
- Add translations for all dashboard widgets (de, es, fr) - Add navigation menu translations for all languages - Create useDateFnsLocale hook for localized date formatting - Add translate="no" to prevent browser auto-translation - Update dashboard components to use translation keys 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" translate="no">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
@@ -92,7 +92,7 @@ const CapacityWidget: React.FC<CapacityWidgetProps> = ({
|
||||
|
||||
<div className={`flex items-center justify-between mb-3 ${isEditing ? 'pl-5' : ''}`}>
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
Capacity This Week
|
||||
{t('dashboard.capacityThisWeek')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Users size={14} className="text-gray-400" />
|
||||
|
||||
@@ -32,11 +32,11 @@ const CustomerBreakdownWidget: React.FC<CustomerBreakdownWidgetProps> = ({
|
||||
newPercentage: total > 0 ? Math.round((newCustomers / total) * 100) : 0,
|
||||
returningPercentage: total > 0 ? Math.round((returning / total) * 100) : 0,
|
||||
chartData: [
|
||||
{ name: 'New', value: newCustomers, color: '#8b5cf6' },
|
||||
{ name: 'Returning', value: returning, color: '#10b981' },
|
||||
{ name: t('dashboard.new'), value: newCustomers, color: '#8b5cf6' },
|
||||
{ name: t('dashboard.returning'), value: returning, color: '#10b981' },
|
||||
],
|
||||
};
|
||||
}, [customers]);
|
||||
}, [customers, t]);
|
||||
|
||||
return (
|
||||
<div className="h-full p-3 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm relative group flex flex-col">
|
||||
@@ -55,7 +55,7 @@ const CustomerBreakdownWidget: React.FC<CustomerBreakdownWidgetProps> = ({
|
||||
)}
|
||||
|
||||
<h3 className={`text-base font-semibold text-gray-900 dark:text-white mb-2 ${isEditing ? 'pl-5' : ''}`}>
|
||||
Customers This Month
|
||||
{t('dashboard.customersThisMonth')}
|
||||
</h3>
|
||||
|
||||
<div className="flex-1 flex items-center gap-3 min-h-0">
|
||||
@@ -88,7 +88,7 @@ const CustomerBreakdownWidget: React.FC<CustomerBreakdownWidgetProps> = ({
|
||||
<UserPlus size={12} className="text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">New</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('dashboard.new')}</p>
|
||||
<p className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{breakdownData.new}{' '}
|
||||
<span className="text-xs font-normal text-gray-400">
|
||||
@@ -103,7 +103,7 @@ const CustomerBreakdownWidget: React.FC<CustomerBreakdownWidgetProps> = ({
|
||||
<UserCheck size={12} className="text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Returning</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('dashboard.returning')}</p>
|
||||
<p className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{breakdownData.returning}{' '}
|
||||
<span className="text-xs font-normal text-gray-400">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
||||
import { GripVertical, X, AlertCircle, Clock, ChevronRight } from 'lucide-react';
|
||||
import { Ticket } from '../../types';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useDateFnsLocale } from '../../hooks/useDateFnsLocale';
|
||||
|
||||
interface OpenTicketsWidgetProps {
|
||||
tickets: Ticket[];
|
||||
@@ -17,6 +18,7 @@ const OpenTicketsWidget: React.FC<OpenTicketsWidgetProps> = ({
|
||||
onRemove,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dateFnsLocale = useDateFnsLocale();
|
||||
const openTickets = tickets.filter(ticket => ticket.status === 'open' || ticket.status === 'in_progress');
|
||||
const urgentCount = openTickets.filter(t => t.priority === 'urgent' || t.isOverdue).length;
|
||||
|
||||
@@ -58,17 +60,17 @@ const OpenTicketsWidget: React.FC<OpenTicketsWidgetProps> = ({
|
||||
|
||||
<div className={`flex items-center justify-between mb-4 ${isEditing ? 'pl-5' : ''}`}>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Open Tickets
|
||||
{t('dashboard.openTickets')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{urgentCount > 0 && (
|
||||
<span className="flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400">
|
||||
<AlertCircle size={12} />
|
||||
{urgentCount} urgent
|
||||
{urgentCount} {t('dashboard.urgent')}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{openTickets.length} open
|
||||
{openTickets.length} {t('dashboard.open')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,11 +95,11 @@ const OpenTicketsWidget: React.FC<OpenTicketsWidgetProps> = ({
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span className={getPriorityColor(ticket.priority, ticket.isOverdue)}>
|
||||
{ticket.isOverdue ? 'Overdue' : ticket.priority}
|
||||
{ticket.isOverdue ? t('dashboard.overdue') : ticket.priority}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock size={10} />
|
||||
{formatDistanceToNow(new Date(ticket.createdAt), { addSuffix: true })}
|
||||
{formatDistanceToNow(new Date(ticket.createdAt), { addSuffix: true, locale: dateFnsLocale })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,7 +115,7 @@ const OpenTicketsWidget: React.FC<OpenTicketsWidgetProps> = ({
|
||||
to="/dashboard/tickets"
|
||||
className="mt-3 text-sm text-brand-600 dark:text-brand-400 hover:underline text-center"
|
||||
>
|
||||
View all {openTickets.length} tickets
|
||||
{t('dashboard.viewAllTickets', { count: openTickets.length })}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { GripVertical, X, Calendar, UserPlus, XCircle, CheckCircle, DollarSign } from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { Appointment, Customer } from '../../types';
|
||||
import { useDateFnsLocale } from '../../hooks/useDateFnsLocale';
|
||||
|
||||
interface ActivityItem {
|
||||
id: string;
|
||||
@@ -28,6 +29,7 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
onRemove,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dateFnsLocale = useDateFnsLocale();
|
||||
const activities = useMemo(() => {
|
||||
const items: ActivityItem[] = [];
|
||||
|
||||
@@ -39,8 +41,8 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
items.push({
|
||||
id: `booking-${appt.id}`,
|
||||
type: 'booking',
|
||||
title: 'New Booking',
|
||||
description: `${appt.customerName} booked an appointment`,
|
||||
title: t('dashboard.newBooking'),
|
||||
description: t('dashboard.customerBookedAppointment', { customerName: appt.customerName }),
|
||||
timestamp,
|
||||
icon: <Calendar size={14} />,
|
||||
iconBg: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400',
|
||||
@@ -49,8 +51,8 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
items.push({
|
||||
id: `cancel-${appt.id}`,
|
||||
type: 'cancellation',
|
||||
title: 'Cancellation',
|
||||
description: `${appt.customerName} cancelled their appointment`,
|
||||
title: t('dashboard.cancellation'),
|
||||
description: t('dashboard.customerCancelledAppointment', { customerName: appt.customerName }),
|
||||
timestamp,
|
||||
icon: <XCircle size={14} />,
|
||||
iconBg: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400',
|
||||
@@ -59,8 +61,8 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
items.push({
|
||||
id: `complete-${appt.id}`,
|
||||
type: 'completion',
|
||||
title: 'Completed',
|
||||
description: `${appt.customerName}'s appointment completed`,
|
||||
title: t('dashboard.completed'),
|
||||
description: t('dashboard.customerAppointmentCompleted', { customerName: appt.customerName }),
|
||||
timestamp,
|
||||
icon: <CheckCircle size={14} />,
|
||||
iconBg: 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400',
|
||||
@@ -76,8 +78,8 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
items.push({
|
||||
id: `customer-${customer.id}`,
|
||||
type: 'new_customer',
|
||||
title: 'New Customer',
|
||||
description: `${customer.name} signed up`,
|
||||
title: t('dashboard.newCustomer'),
|
||||
description: t('dashboard.customerSignedUp', { customerName: customer.name }),
|
||||
timestamp: new Date(), // Approximate - would need createdAt field
|
||||
icon: <UserPlus size={14} />,
|
||||
iconBg: 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400',
|
||||
@@ -107,7 +109,7 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
)}
|
||||
|
||||
<h3 className={`text-lg font-semibold text-gray-900 dark:text-white mb-4 ${isEditing ? 'pl-5' : ''}`}>
|
||||
Recent Activity
|
||||
{t('dashboard.recentActivity')}
|
||||
</h3>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
@@ -131,7 +133,7 @@ const RecentActivityWidget: React.FC<RecentActivityWidgetProps> = ({
|
||||
{activity.description}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
{formatDistanceToNow(activity.timestamp, { addSuffix: true })}
|
||||
{formatDistanceToNow(activity.timestamp, { addSuffix: true, locale: dateFnsLocale })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { X, Plus, Check, LayoutDashboard, BarChart2, Ticket, Activity, Users, UserX, PieChart } from 'lucide-react';
|
||||
import { WIDGET_DEFINITIONS, WidgetType } from './types';
|
||||
import { WIDGET_DEFINITIONS, WidgetType, getWidgetTitle, getWidgetDescription } from './types';
|
||||
|
||||
interface WidgetConfigModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -31,6 +32,7 @@ const WidgetConfigModal: React.FC<WidgetConfigModalProps> = ({
|
||||
onToggleWidget,
|
||||
onResetLayout,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
if (!isOpen) return null;
|
||||
|
||||
const widgets = Object.values(WIDGET_DEFINITIONS);
|
||||
@@ -45,7 +47,7 @@ const WidgetConfigModal: React.FC<WidgetConfigModalProps> = ({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Configure Dashboard Widgets
|
||||
{t('dashboard.configureWidgets')}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -58,7 +60,7 @@ const WidgetConfigModal: React.FC<WidgetConfigModalProps> = ({
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
Select which widgets to show on your dashboard. You can drag widgets to reposition them.
|
||||
{t('dashboard.configureWidgetsDescription')}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
@@ -92,14 +94,14 @@ const WidgetConfigModal: React.FC<WidgetConfigModalProps> = ({
|
||||
: 'text-gray-900 dark:text-white'
|
||||
}`}
|
||||
>
|
||||
{widget.title}
|
||||
{getWidgetTitle(widget.id, t)}
|
||||
</p>
|
||||
{isActive && (
|
||||
<Check size={14} className="text-brand-600 dark:text-brand-400" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{widget.description}
|
||||
{getWidgetDescription(widget.id, t)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
@@ -114,13 +116,13 @@ const WidgetConfigModal: React.FC<WidgetConfigModalProps> = ({
|
||||
onClick={onResetLayout}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
Reset to Default
|
||||
{t('dashboard.resetToDefault')}
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 transition-colors"
|
||||
>
|
||||
Done
|
||||
{t('dashboard.done')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { TFunction } from 'i18next';
|
||||
|
||||
export type WidgetType =
|
||||
| 'appointments-metric'
|
||||
@@ -119,6 +120,39 @@ export const WIDGET_DEFINITIONS: Record<WidgetType, WidgetConfig> = {
|
||||
},
|
||||
};
|
||||
|
||||
// Widget ID to translation key mapping
|
||||
const WIDGET_TRANSLATION_KEYS: Record<WidgetType, string> = {
|
||||
'appointments-metric': 'appointmentsMetric',
|
||||
'customers-metric': 'customersMetric',
|
||||
'services-metric': 'servicesMetric',
|
||||
'resources-metric': 'resourcesMetric',
|
||||
'revenue-chart': 'revenueChart',
|
||||
'appointments-chart': 'appointmentsChart',
|
||||
'open-tickets': 'openTickets',
|
||||
'recent-activity': 'recentActivity',
|
||||
'capacity-utilization': 'capacityUtilization',
|
||||
'no-show-rate': 'noShowRate',
|
||||
'customer-breakdown': 'customerBreakdown',
|
||||
};
|
||||
|
||||
// Helper function to get translated widget title
|
||||
export const getWidgetTitle = (widgetId: string, t: TFunction): string => {
|
||||
const key = WIDGET_TRANSLATION_KEYS[widgetId as WidgetType];
|
||||
if (key) {
|
||||
return t(`dashboard.widgetTitles.${key}`);
|
||||
}
|
||||
return WIDGET_DEFINITIONS[widgetId as WidgetType]?.title || widgetId;
|
||||
};
|
||||
|
||||
// Helper function to get translated widget description
|
||||
export const getWidgetDescription = (widgetId: string, t: TFunction): string => {
|
||||
const key = WIDGET_TRANSLATION_KEYS[widgetId as WidgetType];
|
||||
if (key) {
|
||||
return t(`dashboard.widgetDescriptions.${key}`);
|
||||
}
|
||||
return WIDGET_DEFINITIONS[widgetId as WidgetType]?.description || '';
|
||||
};
|
||||
|
||||
// Default layout for new users
|
||||
export const DEFAULT_LAYOUT: DashboardLayout = {
|
||||
widgets: [
|
||||
|
||||
30
frontend/src/hooks/useDateFnsLocale.ts
Normal file
30
frontend/src/hooks/useDateFnsLocale.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Locale } from 'date-fns';
|
||||
import { enUS, de, es, fr } from 'date-fns/locale';
|
||||
|
||||
const localeMap: Record<string, Locale> = {
|
||||
en: enUS,
|
||||
de: de,
|
||||
es: es,
|
||||
fr: fr,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to get the date-fns locale based on the current i18n language.
|
||||
* Use this with date-fns functions that support locale, like formatDistanceToNow.
|
||||
*
|
||||
* @example
|
||||
* const locale = useDateFnsLocale();
|
||||
* formatDistanceToNow(date, { addSuffix: true, locale });
|
||||
*/
|
||||
export const useDateFnsLocale = (): Locale => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return useMemo(() => {
|
||||
const lang = i18n.language?.split('-')[0] || 'en';
|
||||
return localeMap[lang] || enUS;
|
||||
}, [i18n.language]);
|
||||
};
|
||||
|
||||
export default useDateFnsLocale;
|
||||
@@ -50,10 +50,12 @@
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"scheduler": "Terminplaner",
|
||||
"tasks": "Aufgaben",
|
||||
"customers": "Kunden",
|
||||
"resources": "Ressourcen",
|
||||
"services": "Dienstleistungen",
|
||||
"payments": "Zahlungen",
|
||||
"paymentsDisabledTooltip": "Zahlungen sind deaktiviert. Aktivieren Sie sie in den Geschäftseinstellungen, um Zahlungen von Kunden zu akzeptieren.",
|
||||
"messages": "Nachrichten",
|
||||
"staff": "Personal",
|
||||
"businessSettings": "Geschäftseinstellungen",
|
||||
@@ -65,9 +67,31 @@
|
||||
"platformSettings": "Plattform-Einstellungen",
|
||||
"tickets": "Tickets",
|
||||
"help": "Hilfe",
|
||||
"contracts": "Verträge",
|
||||
"locations": "Standorte",
|
||||
"platformGuide": "Plattform-Handbuch",
|
||||
"ticketingHelp": "Ticket-System",
|
||||
"apiDocs": "API-Dokumentation"
|
||||
"apiDocs": "API-Dokumentation",
|
||||
"automationDocs": "Automatisierungs-Dokumentation",
|
||||
"contactSupport": "Support kontaktieren",
|
||||
"automations": "Automatisierungen",
|
||||
"automationMarketplace": "Marktplatz",
|
||||
"myAutomations": "Meine Automatisierungen",
|
||||
"expandSidebar": "Seitenleiste erweitern",
|
||||
"collapseSidebar": "Seitenleiste einklappen",
|
||||
"smoothSchedule": "Smooth Schedule",
|
||||
"gallery": "Mediengalerie",
|
||||
"siteBuilder": "Website-Baukasten",
|
||||
"mySchedule": "Mein Zeitplan",
|
||||
"myAvailability": "Meine Verfügbarkeit",
|
||||
"timeBlocks": "Zeitblöcke",
|
||||
"helpDocs": "Hilfe & Dokumentation",
|
||||
"sections": {
|
||||
"manage": "Verwalten",
|
||||
"communicate": "Kommunizieren",
|
||||
"money": "Finanzen",
|
||||
"extend": "Erweitern"
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"guide": {
|
||||
@@ -769,7 +793,67 @@
|
||||
"totalRevenue": "Gesamtumsatz",
|
||||
"totalAppointments": "Termine Gesamt",
|
||||
"newCustomers": "Neue Kunden",
|
||||
"pendingPayments": "Ausstehende Zahlungen"
|
||||
"pendingPayments": "Ausstehende Zahlungen",
|
||||
"noResourcesConfigured": "Keine Ressourcen konfiguriert",
|
||||
"noRecentActivity": "Keine aktuellen Aktivitäten",
|
||||
"noOpenTickets": "Keine offenen Tickets",
|
||||
"totalCustomers": "Kunden gesamt",
|
||||
"noShowRate": "No-Show-Quote",
|
||||
"thisMonth": "diesen Monat",
|
||||
"week": "Woche",
|
||||
"month": "Monat",
|
||||
"weekLabel": "Woche:",
|
||||
"monthLabel": "Monat:",
|
||||
"done": "Fertig",
|
||||
"editLayout": "Layout bearbeiten",
|
||||
"widgets": "Widgets",
|
||||
"editModeHint": "Ziehen Sie Widgets, um sie neu zu positionieren. Ziehen Sie die Ecke, um die Größe zu ändern. Fahren Sie mit der Maus über ein Widget und klicken Sie auf X, um es zu entfernen.",
|
||||
"configureWidgets": "Dashboard-Widgets konfigurieren",
|
||||
"configureWidgetsDescription": "Wählen Sie, welche Widgets auf Ihrem Dashboard angezeigt werden sollen. Sie können Widgets ziehen, um sie neu zu positionieren.",
|
||||
"resetToDefault": "Auf Standard zurücksetzen",
|
||||
"openTickets": "Offene Tickets",
|
||||
"urgent": "dringend",
|
||||
"open": "offen",
|
||||
"overdue": "Überfällig",
|
||||
"viewAllTickets": "Alle {{count}} Tickets anzeigen",
|
||||
"newBooking": "Neue Buchung",
|
||||
"customerBookedAppointment": "{{customerName}} hat einen Termin gebucht",
|
||||
"cancellation": "Stornierung",
|
||||
"customerCancelledAppointment": "{{customerName}} hat seinen Termin storniert",
|
||||
"completed": "Abgeschlossen",
|
||||
"customerAppointmentCompleted": "{{customerName}}s Termin wurde abgeschlossen",
|
||||
"newCustomer": "Neuer Kunde",
|
||||
"customerSignedUp": "{{customerName}} hat sich registriert",
|
||||
"capacityThisWeek": "Kapazität diese Woche",
|
||||
"customersThisMonth": "Kunden diesen Monat",
|
||||
"new": "Neu",
|
||||
"returning": "Wiederkehrend",
|
||||
"widgetTitles": {
|
||||
"appointmentsMetric": "Termine gesamt",
|
||||
"customersMetric": "Aktive Kunden",
|
||||
"servicesMetric": "Dienstleistungen",
|
||||
"resourcesMetric": "Ressourcen",
|
||||
"revenueChart": "Umsatz",
|
||||
"appointmentsChart": "Termintrend",
|
||||
"openTickets": "Offene Tickets",
|
||||
"recentActivity": "Letzte Aktivitäten",
|
||||
"capacityUtilization": "Kapazitätsauslastung",
|
||||
"noShowRate": "No-Show-Quote",
|
||||
"customerBreakdown": "Neu vs. Wiederkehrend"
|
||||
},
|
||||
"widgetDescriptions": {
|
||||
"appointmentsMetric": "Zeigt die Terminanzahl mit wöchentlichem und monatlichem Wachstum",
|
||||
"customersMetric": "Zeigt die Kundenanzahl mit wöchentlichem und monatlichem Wachstum",
|
||||
"servicesMetric": "Zeigt die Anzahl der angebotenen Dienstleistungen",
|
||||
"resourcesMetric": "Zeigt die Anzahl der verfügbaren Ressourcen",
|
||||
"revenueChart": "Wöchentliches Umsatz-Balkendiagramm",
|
||||
"appointmentsChart": "Wöchentliches Termin-Liniendiagramm",
|
||||
"openTickets": "Zeigt offene Support-Tickets, die Aufmerksamkeit erfordern",
|
||||
"recentActivity": "Zeitachse der letzten Geschäftsereignisse",
|
||||
"capacityUtilization": "Zeigt, wie ausgebucht Ihre Ressourcen diese Woche sind",
|
||||
"noShowRate": "Prozentsatz der als No-Show markierten Termine",
|
||||
"customerBreakdown": "Kundenaufteilung diesen Monat"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"title": "Terminplaner",
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
"expandSidebar": "Expand sidebar",
|
||||
"collapseSidebar": "Collapse sidebar",
|
||||
"smoothSchedule": "Smooth Schedule",
|
||||
"gallery": "Media Gallery",
|
||||
"siteBuilder": "Site Builder",
|
||||
"mySchedule": "My Schedule",
|
||||
"myAvailability": "My Availability",
|
||||
"timeBlocks": "Time Blocks",
|
||||
"helpDocs": "Help & Docs",
|
||||
"sections": {
|
||||
"manage": "Manage",
|
||||
"communicate": "Communicate",
|
||||
@@ -1322,7 +1328,57 @@
|
||||
"week": "Week",
|
||||
"month": "Month",
|
||||
"weekLabel": "Week:",
|
||||
"monthLabel": "Month:"
|
||||
"monthLabel": "Month:",
|
||||
"done": "Done",
|
||||
"editLayout": "Edit Layout",
|
||||
"widgets": "Widgets",
|
||||
"editModeHint": "Drag widgets to reposition them. Drag the corner to resize. Hover over a widget and click the X to remove it.",
|
||||
"configureWidgets": "Configure Dashboard Widgets",
|
||||
"configureWidgetsDescription": "Select which widgets to show on your dashboard. You can drag widgets to reposition them.",
|
||||
"resetToDefault": "Reset to Default",
|
||||
"openTickets": "Open Tickets",
|
||||
"urgent": "urgent",
|
||||
"open": "open",
|
||||
"overdue": "Overdue",
|
||||
"viewAllTickets": "View all {{count}} tickets",
|
||||
"newBooking": "New Booking",
|
||||
"customerBookedAppointment": "{{customerName}} booked an appointment",
|
||||
"cancellation": "Cancellation",
|
||||
"customerCancelledAppointment": "{{customerName}} cancelled their appointment",
|
||||
"completed": "Completed",
|
||||
"customerAppointmentCompleted": "{{customerName}}'s appointment completed",
|
||||
"newCustomer": "New Customer",
|
||||
"customerSignedUp": "{{customerName}} signed up",
|
||||
"capacityThisWeek": "Capacity This Week",
|
||||
"customersThisMonth": "Customers This Month",
|
||||
"new": "New",
|
||||
"returning": "Returning",
|
||||
"widgetTitles": {
|
||||
"appointmentsMetric": "Total Appointments",
|
||||
"customersMetric": "Active Customers",
|
||||
"servicesMetric": "Services",
|
||||
"resourcesMetric": "Resources",
|
||||
"revenueChart": "Revenue",
|
||||
"appointmentsChart": "Appointments Trend",
|
||||
"openTickets": "Open Tickets",
|
||||
"recentActivity": "Recent Activity",
|
||||
"capacityUtilization": "Capacity Utilization",
|
||||
"noShowRate": "No-Show Rate",
|
||||
"customerBreakdown": "New vs Returning"
|
||||
},
|
||||
"widgetDescriptions": {
|
||||
"appointmentsMetric": "Shows appointment count with weekly and monthly growth",
|
||||
"customersMetric": "Shows customer count with weekly and monthly growth",
|
||||
"servicesMetric": "Shows number of services offered",
|
||||
"resourcesMetric": "Shows number of resources available",
|
||||
"revenueChart": "Weekly revenue bar chart",
|
||||
"appointmentsChart": "Weekly appointments line chart",
|
||||
"openTickets": "Shows open support tickets requiring attention",
|
||||
"recentActivity": "Timeline of recent business events",
|
||||
"capacityUtilization": "Shows how booked your resources are this week",
|
||||
"noShowRate": "Percentage of appointments marked as no-show",
|
||||
"customerBreakdown": "Customer breakdown this month"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"title": "Scheduler",
|
||||
|
||||
@@ -50,10 +50,12 @@
|
||||
"nav": {
|
||||
"dashboard": "Panel",
|
||||
"scheduler": "Agenda",
|
||||
"tasks": "Tareas",
|
||||
"customers": "Clientes",
|
||||
"resources": "Recursos",
|
||||
"services": "Servicios",
|
||||
"payments": "Pagos",
|
||||
"paymentsDisabledTooltip": "Los pagos están desactivados. Actívalos en Configuración del Negocio para aceptar pagos de los clientes.",
|
||||
"messages": "Mensajes",
|
||||
"staff": "Personal",
|
||||
"businessSettings": "Configuración del Negocio",
|
||||
@@ -65,9 +67,31 @@
|
||||
"platformSettings": "Configuración de Plataforma",
|
||||
"tickets": "Tickets",
|
||||
"help": "Ayuda",
|
||||
"contracts": "Contratos",
|
||||
"locations": "Ubicaciones",
|
||||
"platformGuide": "Guía de Plataforma",
|
||||
"ticketingHelp": "Sistema de Tickets",
|
||||
"apiDocs": "Documentación API"
|
||||
"apiDocs": "Documentación API",
|
||||
"automationDocs": "Documentación de Automatización",
|
||||
"contactSupport": "Contactar Soporte",
|
||||
"automations": "Automatizaciones",
|
||||
"automationMarketplace": "Mercado",
|
||||
"myAutomations": "Mis Automatizaciones",
|
||||
"expandSidebar": "Expandir barra lateral",
|
||||
"collapseSidebar": "Contraer barra lateral",
|
||||
"smoothSchedule": "Smooth Schedule",
|
||||
"gallery": "Galería de Medios",
|
||||
"siteBuilder": "Constructor de Sitio",
|
||||
"mySchedule": "Mi Agenda",
|
||||
"myAvailability": "Mi Disponibilidad",
|
||||
"timeBlocks": "Bloques de Tiempo",
|
||||
"helpDocs": "Ayuda y Documentación",
|
||||
"sections": {
|
||||
"manage": "Gestionar",
|
||||
"communicate": "Comunicar",
|
||||
"money": "Dinero",
|
||||
"extend": "Extender"
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"guide": {
|
||||
@@ -829,7 +853,67 @@
|
||||
"totalRevenue": "Ingresos Totales",
|
||||
"totalAppointments": "Citas Totales",
|
||||
"newCustomers": "Nuevos Clientes",
|
||||
"pendingPayments": "Pagos Pendientes"
|
||||
"pendingPayments": "Pagos Pendientes",
|
||||
"noResourcesConfigured": "No hay recursos configurados",
|
||||
"noRecentActivity": "Sin actividad reciente",
|
||||
"noOpenTickets": "No hay tickets abiertos",
|
||||
"totalCustomers": "Clientes totales",
|
||||
"noShowRate": "Tasa de no-show",
|
||||
"thisMonth": "este mes",
|
||||
"week": "Semana",
|
||||
"month": "Mes",
|
||||
"weekLabel": "Semana:",
|
||||
"monthLabel": "Mes:",
|
||||
"done": "Listo",
|
||||
"editLayout": "Editar diseño",
|
||||
"widgets": "Widgets",
|
||||
"editModeHint": "Arrastra los widgets para reposicionarlos. Arrastra la esquina para cambiar el tamaño. Pasa el cursor sobre un widget y haz clic en X para eliminarlo.",
|
||||
"configureWidgets": "Configurar widgets del panel",
|
||||
"configureWidgetsDescription": "Selecciona qué widgets mostrar en tu panel. Puedes arrastrar los widgets para reposicionarlos.",
|
||||
"resetToDefault": "Restablecer valores predeterminados",
|
||||
"openTickets": "Tickets abiertos",
|
||||
"urgent": "urgente",
|
||||
"open": "abierto",
|
||||
"overdue": "Vencido",
|
||||
"viewAllTickets": "Ver todos los {{count}} tickets",
|
||||
"newBooking": "Nueva reserva",
|
||||
"customerBookedAppointment": "{{customerName}} reservó una cita",
|
||||
"cancellation": "Cancelación",
|
||||
"customerCancelledAppointment": "{{customerName}} canceló su cita",
|
||||
"completed": "Completado",
|
||||
"customerAppointmentCompleted": "La cita de {{customerName}} se completó",
|
||||
"newCustomer": "Nuevo cliente",
|
||||
"customerSignedUp": "{{customerName}} se registró",
|
||||
"capacityThisWeek": "Capacidad esta semana",
|
||||
"customersThisMonth": "Clientes este mes",
|
||||
"new": "Nuevo",
|
||||
"returning": "Recurrente",
|
||||
"widgetTitles": {
|
||||
"appointmentsMetric": "Total de citas",
|
||||
"customersMetric": "Clientes activos",
|
||||
"servicesMetric": "Servicios",
|
||||
"resourcesMetric": "Recursos",
|
||||
"revenueChart": "Ingresos",
|
||||
"appointmentsChart": "Tendencia de citas",
|
||||
"openTickets": "Tickets abiertos",
|
||||
"recentActivity": "Actividad reciente",
|
||||
"capacityUtilization": "Utilización de capacidad",
|
||||
"noShowRate": "Tasa de no-show",
|
||||
"customerBreakdown": "Nuevos vs. Recurrentes"
|
||||
},
|
||||
"widgetDescriptions": {
|
||||
"appointmentsMetric": "Muestra el recuento de citas con crecimiento semanal y mensual",
|
||||
"customersMetric": "Muestra el recuento de clientes con crecimiento semanal y mensual",
|
||||
"servicesMetric": "Muestra el número de servicios ofrecidos",
|
||||
"resourcesMetric": "Muestra el número de recursos disponibles",
|
||||
"revenueChart": "Gráfico de barras de ingresos semanales",
|
||||
"appointmentsChart": "Gráfico de líneas de citas semanales",
|
||||
"openTickets": "Muestra tickets de soporte abiertos que requieren atención",
|
||||
"recentActivity": "Línea de tiempo de eventos comerciales recientes",
|
||||
"capacityUtilization": "Muestra qué tan ocupados están tus recursos esta semana",
|
||||
"noShowRate": "Porcentaje de citas marcadas como no-show",
|
||||
"customerBreakdown": "Desglose de clientes este mes"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"title": "Agenda",
|
||||
|
||||
@@ -50,10 +50,12 @@
|
||||
"nav": {
|
||||
"dashboard": "Tableau de Bord",
|
||||
"scheduler": "Planificateur",
|
||||
"tasks": "Tâches",
|
||||
"customers": "Clients",
|
||||
"resources": "Ressources",
|
||||
"services": "Services",
|
||||
"payments": "Paiements",
|
||||
"paymentsDisabledTooltip": "Les paiements sont désactivés. Activez-les dans les Paramètres de l'Entreprise pour accepter les paiements des clients.",
|
||||
"messages": "Messages",
|
||||
"staff": "Personnel",
|
||||
"businessSettings": "Paramètres de l'Entreprise",
|
||||
@@ -65,9 +67,31 @@
|
||||
"platformSettings": "Paramètres Plateforme",
|
||||
"tickets": "Tickets",
|
||||
"help": "Aide",
|
||||
"contracts": "Contrats",
|
||||
"locations": "Emplacements",
|
||||
"platformGuide": "Guide de la Plateforme",
|
||||
"ticketingHelp": "Système de Tickets",
|
||||
"apiDocs": "Documentation API"
|
||||
"apiDocs": "Documentation API",
|
||||
"automationDocs": "Documentation des Automations",
|
||||
"contactSupport": "Contacter le Support",
|
||||
"automations": "Automations",
|
||||
"automationMarketplace": "Marché",
|
||||
"myAutomations": "Mes Automations",
|
||||
"expandSidebar": "Développer la barre latérale",
|
||||
"collapseSidebar": "Réduire la barre latérale",
|
||||
"smoothSchedule": "Smooth Schedule",
|
||||
"gallery": "Galerie Multimédia",
|
||||
"siteBuilder": "Constructeur de Site",
|
||||
"mySchedule": "Mon Emploi du Temps",
|
||||
"myAvailability": "Ma Disponibilité",
|
||||
"timeBlocks": "Blocs de Temps",
|
||||
"helpDocs": "Aide & Documentation",
|
||||
"sections": {
|
||||
"manage": "Gérer",
|
||||
"communicate": "Communiquer",
|
||||
"money": "Argent",
|
||||
"extend": "Étendre"
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"guide": {
|
||||
@@ -769,7 +793,67 @@
|
||||
"totalRevenue": "Revenus Totaux",
|
||||
"totalAppointments": "Total des Rendez-vous",
|
||||
"newCustomers": "Nouveaux Clients",
|
||||
"pendingPayments": "Paiements en Attente"
|
||||
"pendingPayments": "Paiements en Attente",
|
||||
"noResourcesConfigured": "Aucune ressource configurée",
|
||||
"noRecentActivity": "Aucune activité récente",
|
||||
"noOpenTickets": "Aucun ticket ouvert",
|
||||
"totalCustomers": "Clients totaux",
|
||||
"noShowRate": "Taux de no-show",
|
||||
"thisMonth": "ce mois-ci",
|
||||
"week": "Semaine",
|
||||
"month": "Mois",
|
||||
"weekLabel": "Semaine :",
|
||||
"monthLabel": "Mois :",
|
||||
"done": "Terminé",
|
||||
"editLayout": "Modifier la disposition",
|
||||
"widgets": "Widgets",
|
||||
"editModeHint": "Faites glisser les widgets pour les repositionner. Faites glisser le coin pour redimensionner. Survolez un widget et cliquez sur X pour le supprimer.",
|
||||
"configureWidgets": "Configurer les widgets du tableau de bord",
|
||||
"configureWidgetsDescription": "Sélectionnez les widgets à afficher sur votre tableau de bord. Vous pouvez faire glisser les widgets pour les repositionner.",
|
||||
"resetToDefault": "Réinitialiser par défaut",
|
||||
"openTickets": "Tickets ouverts",
|
||||
"urgent": "urgent",
|
||||
"open": "ouvert",
|
||||
"overdue": "En retard",
|
||||
"viewAllTickets": "Voir tous les {{count}} tickets",
|
||||
"newBooking": "Nouvelle réservation",
|
||||
"customerBookedAppointment": "{{customerName}} a réservé un rendez-vous",
|
||||
"cancellation": "Annulation",
|
||||
"customerCancelledAppointment": "{{customerName}} a annulé son rendez-vous",
|
||||
"completed": "Terminé",
|
||||
"customerAppointmentCompleted": "Le rendez-vous de {{customerName}} est terminé",
|
||||
"newCustomer": "Nouveau client",
|
||||
"customerSignedUp": "{{customerName}} s'est inscrit",
|
||||
"capacityThisWeek": "Capacité cette semaine",
|
||||
"customersThisMonth": "Clients ce mois-ci",
|
||||
"new": "Nouveau",
|
||||
"returning": "Récurrent",
|
||||
"widgetTitles": {
|
||||
"appointmentsMetric": "Total des rendez-vous",
|
||||
"customersMetric": "Clients actifs",
|
||||
"servicesMetric": "Services",
|
||||
"resourcesMetric": "Ressources",
|
||||
"revenueChart": "Revenus",
|
||||
"appointmentsChart": "Tendance des rendez-vous",
|
||||
"openTickets": "Tickets ouverts",
|
||||
"recentActivity": "Activité récente",
|
||||
"capacityUtilization": "Utilisation de la capacité",
|
||||
"noShowRate": "Taux de no-show",
|
||||
"customerBreakdown": "Nouveaux vs. Récurrents"
|
||||
},
|
||||
"widgetDescriptions": {
|
||||
"appointmentsMetric": "Affiche le nombre de rendez-vous avec la croissance hebdomadaire et mensuelle",
|
||||
"customersMetric": "Affiche le nombre de clients avec la croissance hebdomadaire et mensuelle",
|
||||
"servicesMetric": "Affiche le nombre de services proposés",
|
||||
"resourcesMetric": "Affiche le nombre de ressources disponibles",
|
||||
"revenueChart": "Graphique à barres des revenus hebdomadaires",
|
||||
"appointmentsChart": "Graphique linéaire des rendez-vous hebdomadaires",
|
||||
"openTickets": "Affiche les tickets de support ouverts nécessitant une attention",
|
||||
"recentActivity": "Chronologie des événements commerciaux récents",
|
||||
"capacityUtilization": "Affiche le taux de réservation de vos ressources cette semaine",
|
||||
"noShowRate": "Pourcentage de rendez-vous marqués comme no-show",
|
||||
"customerBreakdown": "Répartition des clients ce mois-ci"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"title": "Planificateur",
|
||||
|
||||
@@ -385,14 +385,14 @@ const Dashboard: React.FC = () => {
|
||||
}`}
|
||||
>
|
||||
{isEditing ? <Check size={16} /> : <Edit2 size={16} />}
|
||||
<span className="text-sm">{isEditing ? 'Done' : 'Edit Layout'}</span>
|
||||
<span className="text-sm">{isEditing ? t('dashboard.done') : t('dashboard.editLayout')}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowConfig(true)}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<Settings size={16} />
|
||||
<span className="text-sm">Widgets</span>
|
||||
<span className="text-sm">{t('dashboard.widgets')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,7 +400,7 @@ const Dashboard: React.FC = () => {
|
||||
{/* Edit mode hint */}
|
||||
{isEditing && (
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3 text-sm text-blue-700 dark:text-blue-300">
|
||||
Drag widgets to reposition them. Drag the corner to resize. Hover over a widget and click the X to remove it.
|
||||
{t('dashboard.editModeHint')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user