diff --git a/package.json b/package.json index 499d44e..5d9b638 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.20.0", + "version": "1.21.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/globals.css b/src/app/globals.css index d2bf7ac..4eed357 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2051,9 +2051,9 @@ h3 { gap: 0.25rem; padding: 2px 0; background: transparent; - font-family: var(--weekly-time-task-font, var(--weekly-font)); - font-size: var(--weekly-time-task-size, 0.75rem); - font-weight: var(--weekly-time-task-weight, 500); + font-family: var(--weekly-time-task-font, var(--weekly-task-font, var(--weekly-font))); + font-size: var(--weekly-time-task-size, var(--weekly-task-size, 0.9rem)); + font-weight: var(--weekly-time-task-weight, var(--weekly-task-weight, 400)); cursor: pointer; white-space: normal; word-break: break-word; diff --git a/src/components/SimpleDatePicker.tsx b/src/components/SimpleDatePicker.tsx index e4a13e4..9ab002b 100644 --- a/src/components/SimpleDatePicker.tsx +++ b/src/components/SimpleDatePicker.tsx @@ -1,28 +1,54 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { createPortal } from 'react-dom'; import { format, addMonths, subMonths, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, isSameMonth, isSameDay } from 'date-fns'; -import { enUS, de } from 'date-fns/locale'; +import { enUS, de, fr, es, it } from 'date-fns/locale'; interface SimpleDatePickerProps { selected: Date; onSelect: (date: Date) => void; onClose: () => void; language?: string; + anchorRef?: React.RefObject; } -export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en' }: SimpleDatePickerProps) { +export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en', anchorRef }: SimpleDatePickerProps) { const [currentMonth, setCurrentMonth] = useState(new Date(selected)); const modalRef = useRef(null); - const locale = language === 'de' ? de : enUS; + const localeMap: Record = { de, fr, es, it }; + const locale = localeMap[language] || enUS; + const [position, setPosition] = useState<{ top: number; left: number } | null>(null); + + const updatePosition = useCallback(() => { + if (anchorRef?.current) { + const rect = anchorRef.current.getBoundingClientRect(); + setPosition({ + top: rect.bottom + 8, + left: rect.left + rect.width / 2 - 144, // 144 = half of 18rem (288px) + }); + } + }, [anchorRef]); + + useEffect(() => { + updatePosition(); + window.addEventListener('resize', updatePosition); + window.addEventListener('scroll', updatePosition, true); + return () => { + window.removeEventListener('resize', updatePosition); + window.removeEventListener('scroll', updatePosition, true); + }; + }, [updatePosition]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + // Also check if click is on the anchor/trigger button + if (anchorRef?.current && anchorRef.current.contains(event.target as Node)) return; onClose(); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); - }, [onClose]); + }, [onClose, anchorRef]); const renderHeader = () => (
@@ -116,44 +142,40 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language ); }; - return ( + // Use fixed positioning via portal if we have an anchor, otherwise fallback to absolute + const usePortal = !!anchorRef?.current && !!position; + + const pickerContent = (
- {/* Decorative triangle */} -
{renderHeader()} {renderDays()} {renderCells()} -
); + + if (usePortal) { + return createPortal(pickerContent, document.body); + } + + return pickerContent; } diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index ff138db..16e033f 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -426,6 +426,315 @@ const translations: Record = { weekdayCaseCapitalize: "Großbuchstabe (Montag)", weekdayCaseUppercase: "Großbuchstaben (MONTAG)", }, + fr: { + settings: "Paramètres", + general: "Général", + calendar: "Connexions", + localisation: "Localisation", + account: "Compte", + runningList: "Liste continue (reporter les tâches à aujourd'hui)", + protectEventTimes: "Protéger les horaires des événements", + showTimeGrid: "Afficher la grille horaire", + timeSlotDuration: "Durée des créneaux horaires", + viewStyle: "Style d'affichage", + simpleView: "Simple", + calendarView: "Calendrier", + listView: "Liste", + language: "Langue", + dateFormat: "Format de date", + timeFormat: "Format d'heure", + saveChanges: "Enregistrer", + connectedCalendars: "Calendriers connectés", + connectMore: "En connecter d'autres", + connectGoogle: "Connecter Google Agenda", + connectApple: "Connecter le calendrier Apple", + connectSynology: "Connecter Synology", + noCalendars: "Aucun calendrier connecté.", + dataPrivacy: "Données et confidentialité", + downloadData: "Télécharger mes données", + deleteAccount: "Supprimer le compte", + name: "Nom", + email: "E-mail", + timezone: "Fuseau horaire", + changePassword: "Changer le mot de passe", + newPassword: "Nouveau mot de passe", + confirmPassword: "Confirmer le mot de passe", + someday: "UN JOUR", + lists: "Listes", + loading: "Chargement de vos tâches…", + sycing: "Synchronisation…", + synced: "Synchronisé", + localization: "Localisation", + allDayEvents: "ÉVÉNEMENTS JOURNÉE ENTIÈRE", + syncCalendar: "Synchroniser le calendrier", + toggleDarkMode: "Basculer le mode sombre", + signOut: "Se déconnecter", + startHour: "Début de journée", + endHour: "Fin de journée", + weekAbbr: "S", + goalOfWeek: "Objectif de la semaine", + goalScope: "Portée de l'objectif", + goalScopeWeek: "Par semaine", + goalScopeDay: "Par jour", + goalFallback: "Type d'objectif par défaut", + defaultGoal: "Objectif par défaut personnalisé", + showTaskCheckboxes: "Afficher les cases à cocher", + showSomeday: "Afficher la section Un jour", + showAllDay: "Afficher la section Journée entière", + allDayPosition: "Position des événements journée entière", + allDayAbove: "Au-dessus", + allDayBelow: "En dessous", + newPasswordDesc: "Laisser vide pour conserver le mot de passe actuel.", + dateAlignment: "Alignement de la date", + dateVerticalAlign: "Alignement vertical de la date", + alignTop: "Haut", + alignMiddle: "Milieu", + alignBottom: "Bas", + dateLayout: "Disposition de la date", + alignmentLeft: "Gauche", + alignmentCenter: "Centre", + alignmentRight: "Droite", + alignmentTight: "Compact", + backupRestore: "Sauvegarde et restauration", + backupRestoreDesc: "Exportez toutes vos tâches, listes et projets au format JSON. Vous pouvez modifier le fichier et le réimporter.", + exportAllData: "Exporter toutes les données (JSON)", + importData: "Importer des données", + importMode: "Mode d'importation", + importModeMerge: "Fusionner", + importModeMergeDesc: "Ajouter les données importées aux tâches existantes", + importModeReplace: "Remplacer", + importModeReplaceDesc: "Supprimer toutes les données existantes et les remplacer par les données importées", + importReplaceWarning: "Attention : toutes vos tâches, listes et projets actuels seront définitivement supprimés !", + importSelectFile: "Sélectionner un fichier JSON…", + importButton: "Importer", + importing: "Importation…", + exporting: "Exportation…", + projects: "Projets", + projectsDesc: "Organisez vos tâches avec des projets colorés", + addProject: "Ajouter un projet", + projectName: "Nom", + projectColor: "Couleur", + noProjects: "Aucun projet", + assignProject: "Attribuer un projet", + removeProject: "Retirer le projet", + weekdayFormat: "Format des jours", + weekdayFormatFull: "Nom complet (lundi)", + weekdayFormatShort: "Abrégé (lun.)", + weekdayFormatNarrow: "Étroit (L)", + weekdayFormatCustom: "Personnalisé", + customWeekdayNamesMon: "Lu; Ma; Me; Je; Ve; Sa; Di", + customWeekdayNamesSun: "Di; Lu; Ma; Me; Je; Ve; Sa", + weekdayCase: "Casse des jours", + weekdayCaseNormal: "Normal (lundi)", + weekdayCaseCapitalize: "Majuscule (Lundi)", + weekdayCaseUppercase: "Majuscules (LUNDI)", + }, + es: { + settings: "Ajustes", + general: "General", + calendar: "Conexiones", + localisation: "Localización", + account: "Cuenta", + runningList: "Lista continua (pasar tareas a hoy)", + protectEventTimes: "Proteger horarios de eventos", + showTimeGrid: "Mostrar cuadrícula horaria", + timeSlotDuration: "Duración de los intervalos", + viewStyle: "Estilo de vista", + simpleView: "Simple", + calendarView: "Calendario", + listView: "Lista", + language: "Idioma", + dateFormat: "Formato de fecha", + timeFormat: "Formato de hora", + saveChanges: "Guardar cambios", + connectedCalendars: "Calendarios conectados", + connectMore: "Conectar más", + connectGoogle: "Conectar Google Calendar", + connectApple: "Conectar calendario de Apple", + connectSynology: "Conectar Synology", + noCalendars: "No hay calendarios conectados.", + dataPrivacy: "Datos y privacidad", + downloadData: "Descargar mis datos", + deleteAccount: "Eliminar cuenta", + name: "Nombre", + email: "Correo electrónico", + timezone: "Zona horaria", + changePassword: "Cambiar contraseña", + newPassword: "Nueva contraseña", + confirmPassword: "Confirmar contraseña", + someday: "ALGÚN DÍA", + lists: "Listas", + loading: "Cargando tus tareas…", + sycing: "Sincronizando…", + synced: "Sincronizado", + localization: "Localización", + allDayEvents: "EVENTOS DE TODO EL DÍA", + syncCalendar: "Sincronizar calendario", + toggleDarkMode: "Alternar modo oscuro", + signOut: "Cerrar sesión", + startHour: "Inicio del día", + endHour: "Fin del día", + weekAbbr: "S", + goalOfWeek: "Objetivo de la semana", + goalScope: "Alcance del objetivo", + goalScopeWeek: "Por semana", + goalScopeDay: "Por día", + goalFallback: "Tipo de objetivo por defecto", + defaultGoal: "Objetivo predeterminado personalizado", + showTaskCheckboxes: "Mostrar casillas en las tareas", + showSomeday: "Mostrar sección Algún día", + showAllDay: "Mostrar sección Todo el día", + allDayPosition: "Posición de eventos de todo el día", + allDayAbove: "Arriba", + allDayBelow: "Abajo", + newPasswordDesc: "Dejar en blanco para conservar la contraseña actual.", + dateAlignment: "Alineación de la fecha", + dateVerticalAlign: "Alineación vertical de la fecha", + alignTop: "Arriba", + alignMiddle: "Centro", + alignBottom: "Abajo", + dateLayout: "Disposición de la fecha", + alignmentLeft: "Izquierda", + alignmentCenter: "Centro", + alignmentRight: "Derecha", + alignmentTight: "Compacto", + backupRestore: "Copia de seguridad y restauración", + backupRestoreDesc: "Exporta todas tus tareas, listas y proyectos como archivo JSON. Puedes editar el archivo y volver a importarlo.", + exportAllData: "Exportar todos los datos (JSON)", + importData: "Importar datos", + importMode: "Modo de importación", + importModeMerge: "Combinar", + importModeMergeDesc: "Añadir los datos importados junto a las tareas existentes", + importModeReplace: "Reemplazar", + importModeReplaceDesc: "Eliminar todos los datos existentes y reemplazarlos con los datos importados", + importReplaceWarning: "Advertencia: ¡Se eliminarán permanentemente todas tus tareas, listas y proyectos actuales!", + importSelectFile: "Seleccionar archivo JSON…", + importButton: "Importar", + importing: "Importando…", + exporting: "Exportando…", + projects: "Proyectos", + projectsDesc: "Organiza las tareas con proyectos de colores", + addProject: "Añadir proyecto", + projectName: "Nombre", + projectColor: "Color", + noProjects: "Aún no hay proyectos", + assignProject: "Asignar proyecto", + removeProject: "Quitar proyecto", + weekdayFormat: "Formato de los días", + weekdayFormatFull: "Nombre completo (lunes)", + weekdayFormatShort: "Abreviado (lun.)", + weekdayFormatNarrow: "Estrecho (L)", + weekdayFormatCustom: "Personalizado", + customWeekdayNamesMon: "Lu; Ma; Mi; Ju; Vi; Sá; Do", + customWeekdayNamesSun: "Do; Lu; Ma; Mi; Ju; Vi; Sá", + weekdayCase: "Mayúsculas de los días", + weekdayCaseNormal: "Normal (lunes)", + weekdayCaseCapitalize: "Mayúscula inicial (Lunes)", + weekdayCaseUppercase: "Mayúsculas (LUNES)", + }, + it: { + settings: "Impostazioni", + general: "Generali", + calendar: "Connessioni", + localisation: "Localizzazione", + account: "Account", + runningList: "Lista continua (sposta le attività a oggi)", + protectEventTimes: "Proteggi gli orari degli eventi", + showTimeGrid: "Mostra griglia oraria", + timeSlotDuration: "Durata degli intervalli", + viewStyle: "Stile di visualizzazione", + simpleView: "Semplice", + calendarView: "Calendario", + listView: "Lista", + language: "Lingua", + dateFormat: "Formato data", + timeFormat: "Formato ora", + saveChanges: "Salva modifiche", + connectedCalendars: "Calendari collegati", + connectMore: "Collega altri", + connectGoogle: "Collega Google Calendar", + connectApple: "Collega il calendario Apple", + connectSynology: "Collega Synology", + noCalendars: "Nessun calendario collegato.", + dataPrivacy: "Dati e privacy", + downloadData: "Scarica i miei dati", + deleteAccount: "Elimina account", + name: "Nome", + email: "E-mail", + timezone: "Fuso orario", + changePassword: "Cambia password", + newPassword: "Nuova password", + confirmPassword: "Conferma password", + someday: "UN GIORNO", + lists: "Liste", + loading: "Caricamento delle attività…", + sycing: "Sincronizzazione…", + synced: "Sincronizzato", + localization: "Localizzazione", + allDayEvents: "EVENTI GIORNATA INTERA", + syncCalendar: "Sincronizza calendario", + toggleDarkMode: "Attiva/disattiva modalità scura", + signOut: "Esci", + startHour: "Inizio giornata", + endHour: "Fine giornata", + weekAbbr: "S", + goalOfWeek: "Obiettivo della settimana", + goalScope: "Ambito dell'obiettivo", + goalScopeWeek: "Per settimana", + goalScopeDay: "Per giorno", + goalFallback: "Tipo di obiettivo predefinito", + defaultGoal: "Obiettivo predefinito personalizzato", + showTaskCheckboxes: "Mostra caselle di spunta sulle attività", + showSomeday: "Mostra sezione Un giorno", + showAllDay: "Mostra sezione Giornata intera", + allDayPosition: "Posizione eventi giornata intera", + allDayAbove: "Sopra", + allDayBelow: "Sotto", + newPasswordDesc: "Lascia vuoto per mantenere la password attuale.", + dateAlignment: "Allineamento della data", + dateVerticalAlign: "Allineamento verticale della data", + alignTop: "In alto", + alignMiddle: "Al centro", + alignBottom: "In basso", + dateLayout: "Disposizione della data", + alignmentLeft: "Sinistra", + alignmentCenter: "Centro", + alignmentRight: "Destra", + alignmentTight: "Compatto", + backupRestore: "Backup e ripristino", + backupRestoreDesc: "Esporta tutte le attività, le liste e i progetti come file JSON. Puoi modificare il file e reimportarlo.", + exportAllData: "Esporta tutti i dati (JSON)", + importData: "Importa dati", + importMode: "Modalità di importazione", + importModeMerge: "Unisci", + importModeMergeDesc: "Aggiungere i dati importati alle attività esistenti", + importModeReplace: "Sostituisci", + importModeReplaceDesc: "Elimina tutti i dati esistenti e sostituiscili con i dati importati", + importReplaceWarning: "Attenzione: tutte le attività, le liste e i progetti attuali verranno eliminati definitivamente!", + importSelectFile: "Seleziona file JSON…", + importButton: "Importa", + importing: "Importazione…", + exporting: "Esportazione…", + projects: "Progetti", + projectsDesc: "Organizza le attività con progetti colorati", + addProject: "Aggiungi progetto", + projectName: "Nome", + projectColor: "Colore", + noProjects: "Nessun progetto", + assignProject: "Assegna progetto", + removeProject: "Rimuovi progetto", + weekdayFormat: "Formato dei giorni", + weekdayFormatFull: "Nome completo (lunedì)", + weekdayFormatShort: "Abbreviato (lun)", + weekdayFormatNarrow: "Stretto (L)", + weekdayFormatCustom: "Personalizzato", + customWeekdayNamesMon: "Lu; Ma; Me; Gi; Ve; Sa; Do", + customWeekdayNamesSun: "Do; Lu; Ma; Me; Gi; Ve; Sa", + weekdayCase: "Maiuscole dei giorni", + weekdayCaseNormal: "Normale (lunedì)", + weekdayCaseCapitalize: "Iniziale maiuscola (Lunedì)", + weekdayCaseUppercase: "Maiuscolo (LUNEDÌ)", + }, }; // Date utilities @@ -884,8 +1193,8 @@ export default function WeeklyView() { yearFontFamily: "Oswald", yearFontSize: "1.5rem", yearFontWeight: "700", - quoteSourceUrl: "https://recite.vercel.app/api/random", - quoteSourceUrls: ["https://recite.vercel.app/api/random"], + quoteSourceUrl: "", + quoteSourceUrls: [], }); const [motivationalQuote, setMotivationalQuote] = useState(""); const [showSummary, setShowSummary] = useState(false); @@ -946,6 +1255,7 @@ export default function WeeklyView() { const [isSearchOpen, setIsSearchOpen] = useState(false); const [isRecurringTasksOpen, setIsRecurringTasksOpen] = useState(false); const [showDatePicker, setShowDatePicker] = useState(false); + const datePickerBtnRef = useRef(null); const [showQuickSettings, setShowQuickSettings] = useState(false); const [focusTimerDuration, setFocusTimerDuration] = useState(25); @@ -3802,13 +4112,27 @@ export default function WeeklyView() { : "var(--weekly-font-headline)", "--weekly-date-size": scaleRem(profile.dateFontSize || "0.65rem"), "--weekly-date-weight": profile.dateFontWeight || "400", - "--weekly-time-task-font": fontVal(profile.timeTaskFontFamily) - ? `"${fontVal(profile.timeTaskFontFamily)}", sans-serif` - : fontVal(profile.taskFontFamily) - ? `"${fontVal(profile.taskFontFamily)}", sans-serif` - : "var(--weekly-font)", - "--weekly-time-task-size": scaleRem(profile.timeTaskFontSize || profile.taskFontSize || "0.9rem"), - "--weekly-time-task-weight": profile.timeTaskFontWeight || profile.taskFontWeight || "400", + "--weekly-time-task-font": (() => { + // If time task font is explicitly customized (not default "Inter"), use it + // Otherwise inherit from task font + const ttf = fontVal(profile.timeTaskFontFamily); + const tf = fontVal(profile.taskFontFamily); + const isDefault = !ttf || ttf === "Inter"; + if (!isDefault) return `"${ttf}", sans-serif`; + if (tf) return `"${tf}", sans-serif`; + return "var(--weekly-font)"; + })(), + "--weekly-time-task-size": scaleRem( + // If time task size is the old default 0.75rem, use task size instead + profile.timeTaskFontSize && profile.timeTaskFontSize !== "0.75rem" + ? profile.timeTaskFontSize + : profile.taskFontSize || "0.9rem" + ), + "--weekly-time-task-weight": + // If time task weight is the old default 500, use task weight instead + profile.timeTaskFontWeight && profile.timeTaskFontWeight !== "500" + ? profile.timeTaskFontWeight + : profile.taskFontWeight || "400", "--weekly-font": "var(--font-body)" /* Force default body font as requested */, "--weekly-task-font": fontVal(profile.taskFontFamily) @@ -4758,8 +5082,9 @@ export default function WeeklyView() {
{/* Date Picker Toggle */} -
+
@@ -8141,7 +8467,7 @@ function SettingsSidebar({ showSubHourSlots: true, allDayPosition: "below", goalFallbackType: "quote", - quoteSourceUrl: "https://recite.vercel.app/api/random", + quoteSourceUrl: "", headlineFont: "Inter", headlineFontSize: "1.25rem", headlineFontWeight: "900", @@ -9553,9 +9879,10 @@ function SettingsSidebar({ }} > - - - + + + +
@@ -11378,13 +11705,13 @@ function SettingsSidebar({ API-Datenquellen (URLs)
- {(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]).map((url: string, idx: number) => ( + {(profile.quoteSourceUrls || [profile.quoteSourceUrl || ""]).map((url: string, idx: number) => (
{ - const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"])]; + const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || ""])]; newUrls[idx] = e.target.value; setProfile((p) => ({ ...p, quoteSourceUrls: newUrls })); }} @@ -11402,7 +11729,7 @@ function SettingsSidebar({