Refactor localization handling across components
- Updated LocalizationContext to consolidate date and time formatting functions into a single `fmt` object. - Modified ChoreCardHelpers to accept a time format parameter for due date text. - Adjusted ChoreView, RepeatSection, TimePassedCard, and other components to utilize the new `fmt` object for date and time formatting. - Enhanced HistoryCard and TimerEditModal to use the new localization functions for consistent date and time display. - Refined Settings and CircleSettings to leverage the updated localization methods for subscription and member date displays. - Improved ThingsHistory and UserActivities to format dates and times using the new localization structure. - Ensured CalendarCard and CalendarDual components utilize the new formatting functions for better readability.
This commit is contained in:
75
public/locales/fr/chores.json
Normal file
75
public/locales/fr/chores.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"title": "Tâches",
|
||||
"myChores": "Mes Tâches",
|
||||
"allChores": "Toutes les Tâches",
|
||||
"addChore": "Ajouter une Tâche",
|
||||
"editChore": "Modifier la Tâche",
|
||||
"deleteChore": "Supprimer la Tâche",
|
||||
"completeChore": "Terminer la Tâche",
|
||||
"dueDate": "Date d'Échéance",
|
||||
"assignedTo": "Assigné à",
|
||||
"priority": "Priorité",
|
||||
"status": "Statut",
|
||||
"description": "Description",
|
||||
"choreView": {
|
||||
"assignment": "Attribution",
|
||||
"assigned": "Assigné",
|
||||
"last": "Dernier",
|
||||
"schedule": "Calendrier",
|
||||
"due": "Échéance",
|
||||
"statistics": "Statistiques",
|
||||
"completed": "Terminé",
|
||||
"times": "fois",
|
||||
"details": "Détails",
|
||||
"createdBy": "Créé par",
|
||||
"na": "N/A",
|
||||
"taskCompleted": "Tâche Terminée",
|
||||
"taskCompletedMessage": "Votre tâche a été marquée comme terminée",
|
||||
"taskCompletionUndone": "La complétion de la tâche a été annulée.",
|
||||
"taskSkipUndone": "Le saut de tâche a été annulé.",
|
||||
"undoSuccessful": "Annulation Réussie",
|
||||
"undoFailed": "Annulation Échouée",
|
||||
"undoFailedMessage": "Impossible d'annuler l'action. Veuillez réessayer.",
|
||||
"resetTimer": "Réinitialiser le Minuteur",
|
||||
"resetTimerConfirmation": "Êtes-vous sûr de vouloir réinitialiser le minuteur ? Cela effacera tous les enregistrements de temps depuis le début de la tâche.",
|
||||
"clearAllTimeRecords": "Effacer Tous les Enregistrements de Temps",
|
||||
"clearAllTimeConfirmation": "Cela supprimera définitivement tous les minuteurs de cette tâche et la remettra à l'état \"non démarrée\".",
|
||||
"descriptionTitle": "Description",
|
||||
"description": "Description :",
|
||||
"previousNote": "Note Précédente",
|
||||
"previousNoteLabel": "Note précédente :",
|
||||
"subtasksLabel": "Sous-tâches :",
|
||||
"taskActions": "Actions de Tâche",
|
||||
"addNote": "Ajouter une note",
|
||||
"additionalNotes": "Notes Supplémentaires :",
|
||||
"notePlaceholder": "Ajouter une note sur la complétion...",
|
||||
"setCustomCompletionTime": "Définir une heure de complétion personnalisée",
|
||||
"skipTask": "Passer la Tâche",
|
||||
"skipTaskConfirmation": "Êtes-vous sûr de vouloir passer cette tâche ?",
|
||||
"markComplete": "Marquer comme Terminée",
|
||||
"markAsDone": "Marquer comme fait",
|
||||
"edit": "Modifier",
|
||||
"archive": "Archiver",
|
||||
"unarchive": "Désarchiver",
|
||||
"viewHistory": "Voir l'Historique",
|
||||
"history": "Historique",
|
||||
"startTimer": "Démarrer le Minuteur",
|
||||
"start": "Démarrer",
|
||||
"pauseTimer": "Mettre en Pause le Minuteur",
|
||||
"approve": "Approuver",
|
||||
"reject": "Rejeter",
|
||||
"pendingApproval": "En Attente d'Approbation",
|
||||
"undo": "Annuler",
|
||||
"skip": "Passer",
|
||||
"cancel": "Annuler",
|
||||
"noPriority": "Aucune Priorité",
|
||||
"subtasks": "Sous-tâches",
|
||||
"noDescription": "Aucune description disponible",
|
||||
"timer": {
|
||||
"active": "Minuteur Actif",
|
||||
"paused": "Minuteur en Pause",
|
||||
"reset": "Réinitialiser le Minuteur",
|
||||
"delete": "Supprimer la Session"
|
||||
}
|
||||
}
|
||||
}
|
||||
174
public/locales/fr/settings.json
Normal file
174
public/locales/fr/settings.json
Normal file
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"title": "Paramètres",
|
||||
"circleSettings": {
|
||||
"title": "Paramètres du cercle",
|
||||
"description": "Votre compte est automatiquement connecté à un Cercle lorsque vous en créez un ou en rejoignez un. Invitez facilement des amis en partageant le code unique du Cercle ou le lien ci-dessous.",
|
||||
"circleCode": "Code du Cercle",
|
||||
"copyCode": "Copier le Code",
|
||||
"copyLink": "Copier le Lien",
|
||||
"codeCopied": "Code du cercle copié !",
|
||||
"linkCopied": "Lien copié !",
|
||||
"joinCircle": "Rejoindre un Cercle",
|
||||
"joinCirclePlaceholder": "Entrer le Code du Cercle",
|
||||
"join": "Rejoindre",
|
||||
"leave": "Quitter le Cercle",
|
||||
"leaveConfirmTitle": "Quitter le Cercle",
|
||||
"leaveConfirmMessage": "Êtes-vous sûr de vouloir quitter ce cercle ?",
|
||||
"circleMembers": "Membres du Cercle",
|
||||
"circleMemberRequests": "Demandes de Membres du Cercle",
|
||||
"admin": "Administrateur",
|
||||
"member": "Membre",
|
||||
"pending": "En attente",
|
||||
"accept": "Accepter",
|
||||
"reject": "Rejeter",
|
||||
"makeAdmin": "Nommer Administrateur",
|
||||
"makeMember": "Nommer Membre",
|
||||
"remove": "Supprimer",
|
||||
"webhookURL": "URL du Webhook",
|
||||
"webhookDescription": "Entrez une URL de webhook pour recevoir des notifications pour les événements du cercle",
|
||||
"webhookPlaceholder": "https://votre-url-webhook.com"
|
||||
},
|
||||
"accountSettings": {
|
||||
"title": "Paramètres du Compte",
|
||||
"subscription": "Abonnement",
|
||||
"subscriptionStatus": "Plan Actuel",
|
||||
"free": "Gratuit",
|
||||
"plus": "Plus",
|
||||
"upgrade": "Mettre à Niveau",
|
||||
"cancel": "Annuler",
|
||||
"changePassword": "Changer le Mot de Passe",
|
||||
"password": "Mot de passe",
|
||||
"dangerZone": "Zone Dangereuse",
|
||||
"dangerZoneDescription": "Une fois votre compte supprimé, il n'y a pas de retour en arrière. Veuillez être certain.",
|
||||
"deleteAccount": "Supprimer le Compte"
|
||||
},
|
||||
"localization": {
|
||||
"title": "Localisation",
|
||||
"description": "Personnalisez la langue, le format de date et les préférences régionales pour votre compte.",
|
||||
"language": "Langue",
|
||||
"languageDescription": "Sélectionnez votre langue préférée",
|
||||
"dateFormat": "Format de Date",
|
||||
"dateFormatDescription": "Choisissez comment les dates doivent être affichées dans l'application",
|
||||
"timeFormat": "Format de l'Heure",
|
||||
"timeFormatDescription": "Sélectionnez le format 12 heures ou 24 heures",
|
||||
"12hour": "12 heures (AM/PM)",
|
||||
"24hour": "24 heures",
|
||||
"firstDayOfWeek": "Premier Jour de la Semaine",
|
||||
"firstDayOfWeekDescription": "Sélectionnez quel jour commence votre semaine",
|
||||
"sunday": "Dimanche",
|
||||
"monday": "Lundi",
|
||||
"saturday": "Samedi",
|
||||
"formats": {
|
||||
"mdy": "MM/JJ/AAAA (États-Unis)",
|
||||
"dmy": "JJ/MM/AAAA (Europe)",
|
||||
"ymd": "AAAA-MM-JJ (ISO)",
|
||||
"long": "Format long (ex., 1 janvier 2024)",
|
||||
"short": "Format court (ex., 1 janv. 2024)"
|
||||
}
|
||||
},
|
||||
"sidepanel": {
|
||||
"title": "Personnalisation du Panneau Latéral",
|
||||
"description": "Personnalisez la disposition et la visibilité des cartes dans le panneau latéral. Cette section n'est disponible que sur les grands écrans comme les tablettes et les ordinateurs de bureau."
|
||||
},
|
||||
"theme": {
|
||||
"title": "Préférences de thème",
|
||||
"description": "Choisissez comment le site vous apparaît. Sélectionnez un thème unique ou synchronisez avec votre système pour basculer automatiquement entre les thèmes jour et nuit.",
|
||||
"themeMode": "Mode de thème",
|
||||
"light": "Clair",
|
||||
"dark": "Sombre",
|
||||
"system": "Système"
|
||||
},
|
||||
"notifications": {
|
||||
"settingsSaved": "Paramètres enregistrés avec succès",
|
||||
"settingsSaveFailed": "Échec de l'enregistrement des paramètres",
|
||||
"invalidWebhook": "URL de webhook invalide"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Paramètres du Profil",
|
||||
"description": "Mettez à jour votre nom d'affichage et votre photo de profil.",
|
||||
"photoUpdated": "Photo Mise à Jour",
|
||||
"photoUpdatedMessage": "Votre photo de profil a été mise à jour avec succès !",
|
||||
"uploadFailed": "Échec du Téléchargement",
|
||||
"uploadFailedMessage": "Échec du téléchargement de votre photo. Veuillez réessayer.",
|
||||
"profileUpdated": "Profil Mis à Jour",
|
||||
"profileUpdatedMessage": "Vos informations de profil ont été enregistrées avec succès !",
|
||||
"updateFailed": "Échec de la Mise à Jour",
|
||||
"updateFailedMessage": "Impossible de mettre à jour votre profil. Veuillez vérifier votre connexion et réessayer.",
|
||||
"changePhoto": "Changer la Photo",
|
||||
"displayName": "Nom d'Affichage",
|
||||
"displayNamePlaceholder": "Entrez votre nom d'affichage",
|
||||
"timezone": "Fuseau Horaire",
|
||||
"timezonePlaceholder": "Sélectionnez votre fuseau horaire",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Paramètres",
|
||||
"subtitle": "Personnalisez votre expérience et gérez les préférences de votre compte",
|
||||
"upgrade": {
|
||||
"title": "Passer à Plus",
|
||||
"description": "Débloquez des fonctionnalités puissantes pour améliorer votre productivité",
|
||||
"button": "Mettre à Niveau Maintenant",
|
||||
"features": {
|
||||
"richText": "Descriptions en texte enrichi",
|
||||
"notifications": "Notifications de tâches",
|
||||
"apiIntegrations": "Intégrations API",
|
||||
"advancedAutomation": "Automatisation avancée"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"profile": {
|
||||
"title": "Paramètres du Profil",
|
||||
"description": "Mettez à jour vos informations de profil, photo, nom d'affichage et préférences de fuseau horaire."
|
||||
},
|
||||
"circle": {
|
||||
"title": "Paramètres du Cercle",
|
||||
"description": "Gérez votre cercle, invitez des membres et gérez les demandes d'adhésion."
|
||||
},
|
||||
"account": {
|
||||
"title": "Paramètres du Compte",
|
||||
"description": "Gérez votre abonnement, changez le mot de passe et les options de suppression de compte."
|
||||
},
|
||||
"subaccounts": {
|
||||
"title": "Comptes Gérés",
|
||||
"description": "Créez et gérez des sous-comptes pour vous connecter et accomplir les tâches assignées."
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Configurez les notifications push, les alertes par e-mail et les cibles de notification pour les tâches."
|
||||
},
|
||||
"mfa": {
|
||||
"title": "Authentification Multi-Facteurs",
|
||||
"description": "Ajoutez une couche de sécurité supplémentaire avec MFA en utilisant des applications d'authentification."
|
||||
},
|
||||
"apitokens": {
|
||||
"title": "Jetons API",
|
||||
"description": "Générez et gérez des jetons d'accès pour les intégrations tierces et l'accès à l'API."
|
||||
},
|
||||
"storage": {
|
||||
"title": "Paramètres de Stockage",
|
||||
"description": "Sauvegardez et restaurez vos données, gérez le stockage local et les préférences de synchronisation."
|
||||
},
|
||||
"sidepanel": {
|
||||
"title": "Personnalisation du Panneau Latéral",
|
||||
"description": "Personnalisez la disposition et la visibilité des cartes dans l'interface du panneau latéral."
|
||||
},
|
||||
"theme": {
|
||||
"title": "Préférences de Thème",
|
||||
"description": "Choisissez votre thème préféré et configurez les paramètres de mode sombre/clair."
|
||||
},
|
||||
"localization": {
|
||||
"title": "Localisation",
|
||||
"description": "Personnalisez la langue, le format de date, le format de l'heure et les préférences régionales."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Paramètres Avancés",
|
||||
"description": "Configurez les webhooks, les mises à jour en temps réel et d'autres fonctionnalités avancées pour améliorer la productivité."
|
||||
},
|
||||
"developer": {
|
||||
"title": "Paramètres Développeur",
|
||||
"description": "Consultez les informations techniques sur les jetons d'authentification, les connexions SSE et les données de débogage."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
public/locales/nl/chores.json
Normal file
75
public/locales/nl/chores.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"title": "Taken",
|
||||
"myChores": "Mijn Taken",
|
||||
"allChores": "Alle Taken",
|
||||
"addChore": "Taak Toevoegen",
|
||||
"editChore": "Taak Bewerken",
|
||||
"deleteChore": "Taak Verwijderen",
|
||||
"completeChore": "Taak Voltooien",
|
||||
"dueDate": "Vervaldatum",
|
||||
"assignedTo": "Toegewezen aan",
|
||||
"priority": "Prioriteit",
|
||||
"status": "Status",
|
||||
"description": "Beschrijving",
|
||||
"choreView": {
|
||||
"assignment": "Toewijzing",
|
||||
"assigned": "Toegewezen",
|
||||
"last": "Laatste",
|
||||
"schedule": "Schema",
|
||||
"due": "Vervaldatum",
|
||||
"statistics": "Statistieken",
|
||||
"completed": "Voltooid",
|
||||
"times": "keer",
|
||||
"details": "Details",
|
||||
"createdBy": "Aangemaakt door",
|
||||
"na": "N/B",
|
||||
"taskCompleted": "Taak Voltooid",
|
||||
"taskCompletedMessage": "Uw taak is gemarkeerd als voltooid",
|
||||
"taskCompletionUndone": "De voltooiing van de taak is ongedaan gemaakt.",
|
||||
"taskSkipUndone": "Het overslaan van de taak is ongedaan gemaakt.",
|
||||
"undoSuccessful": "Ongedaan Maken Geslaagd",
|
||||
"undoFailed": "Ongedaan Maken Mislukt",
|
||||
"undoFailedMessage": "Kan de actie niet ongedaan maken. Probeer het opnieuw.",
|
||||
"resetTimer": "Timer Resetten",
|
||||
"resetTimerConfirmation": "Weet u zeker dat u de timer wilt resetten? Dit wist alle tijdregistraties sinds u de taak bent begonnen.",
|
||||
"clearAllTimeRecords": "Alle Tijdregistraties Wissen",
|
||||
"clearAllTimeConfirmation": "Dit verwijdert permanent alle timers voor deze taak en zet hem terug naar \"niet gestart\".",
|
||||
"descriptionTitle": "Beschrijving",
|
||||
"description": "Beschrijving :",
|
||||
"previousNote": "Vorige Notitie",
|
||||
"previousNoteLabel": "Vorige notitie:",
|
||||
"subtasksLabel": "Subtaken :",
|
||||
"taskActions": "Taakacties",
|
||||
"addNote": "Een notitie toevoegen",
|
||||
"additionalNotes": "Aanvullende Notities:",
|
||||
"notePlaceholder": "Voeg een notitie toe over de voltooiing...",
|
||||
"setCustomCompletionTime": "Aangepaste voltooitijd instellen",
|
||||
"skipTask": "Taak Overslaan",
|
||||
"skipTaskConfirmation": "Weet u zeker dat u deze taak wilt overslaan?",
|
||||
"markComplete": "Markeren als Voltooid",
|
||||
"markAsDone": "Markeren als gedaan",
|
||||
"edit": "Bewerken",
|
||||
"archive": "Archiveren",
|
||||
"unarchive": "Dearchiveren",
|
||||
"viewHistory": "Geschiedenis Bekijken",
|
||||
"history": "Geschiedenis",
|
||||
"startTimer": "Timer Starten",
|
||||
"start": "Starten",
|
||||
"pauseTimer": "Timer Pauzeren",
|
||||
"approve": "Goedkeuren",
|
||||
"reject": "Afwijzen",
|
||||
"pendingApproval": "In Afwachting van Goedkeuring",
|
||||
"undo": "Ongedaan Maken",
|
||||
"skip": "Overslaan",
|
||||
"cancel": "Annuleren",
|
||||
"noPriority": "Geen Prioriteit",
|
||||
"subtasks": "Subtaken",
|
||||
"noDescription": "Geen beschrijving beschikbaar",
|
||||
"timer": {
|
||||
"active": "Timer Actief",
|
||||
"paused": "Timer Gepauzeerd",
|
||||
"reset": "Timer Resetten",
|
||||
"delete": "Sessie Verwijderen"
|
||||
}
|
||||
}
|
||||
}
|
||||
174
public/locales/nl/settings.json
Normal file
174
public/locales/nl/settings.json
Normal file
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"title": "Instellingen",
|
||||
"circleSettings": {
|
||||
"title": "Cirkelinstellingen",
|
||||
"description": "Uw account wordt automatisch verbonden met een Cirkel wanneer u er een aanmaakt of lid wordt. Nodig eenvoudig vrienden uit door de unieke Cirkelcode of onderstaande link te delen.",
|
||||
"circleCode": "Cirkelcode",
|
||||
"copyCode": "Code Kopiëren",
|
||||
"copyLink": "Link Kopiëren",
|
||||
"codeCopied": "Cirkelcode gekopieerd!",
|
||||
"linkCopied": "Link gekopieerd!",
|
||||
"joinCircle": "Lid Worden van een Cirkel",
|
||||
"joinCirclePlaceholder": "Voer Cirkelcode in",
|
||||
"join": "Lid Worden",
|
||||
"leave": "Cirkel Verlaten",
|
||||
"leaveConfirmTitle": "Cirkel Verlaten",
|
||||
"leaveConfirmMessage": "Weet u zeker dat u deze cirkel wilt verlaten?",
|
||||
"circleMembers": "Cirkelleden",
|
||||
"circleMemberRequests": "Aanvragen voor Cirkellidmaatschap",
|
||||
"admin": "Beheerder",
|
||||
"member": "Lid",
|
||||
"pending": "In behandeling",
|
||||
"accept": "Accepteren",
|
||||
"reject": "Afwijzen",
|
||||
"makeAdmin": "Beheerder Maken",
|
||||
"makeMember": "Lid Maken",
|
||||
"remove": "Verwijderen",
|
||||
"webhookURL": "Webhook URL",
|
||||
"webhookDescription": "Voer een webhook URL in om meldingen te ontvangen voor cirkelgebeurtenissen",
|
||||
"webhookPlaceholder": "https://uw-webhook-url.com"
|
||||
},
|
||||
"accountSettings": {
|
||||
"title": "Accountinstellingen",
|
||||
"subscription": "Abonnement",
|
||||
"subscriptionStatus": "Huidig Plan",
|
||||
"free": "Gratis",
|
||||
"plus": "Plus",
|
||||
"upgrade": "Upgraden",
|
||||
"cancel": "Annuleren",
|
||||
"changePassword": "Wachtwoord Wijzigen",
|
||||
"password": "Wachtwoord",
|
||||
"dangerZone": "Gevarenzone",
|
||||
"dangerZoneDescription": "Zodra u uw account verwijdert, is er geen weg terug. Wees zeker van uw beslissing.",
|
||||
"deleteAccount": "Account Verwijderen"
|
||||
},
|
||||
"localization": {
|
||||
"title": "Lokalisatie",
|
||||
"description": "Pas de taal, datumnotatie en regionale voorkeuren voor uw account aan.",
|
||||
"language": "Taal",
|
||||
"languageDescription": "Selecteer uw voorkeurstaal",
|
||||
"dateFormat": "Datumnotatie",
|
||||
"dateFormatDescription": "Kies hoe datums in de applicatie worden weergegeven",
|
||||
"timeFormat": "Tijdnotatie",
|
||||
"timeFormatDescription": "Selecteer 12-uurs of 24-uurs tijdnotatie",
|
||||
"12hour": "12-uurs (AM/PM)",
|
||||
"24hour": "24-uurs",
|
||||
"firstDayOfWeek": "Eerste Dag van de Week",
|
||||
"firstDayOfWeekDescription": "Selecteer welke dag uw week begint",
|
||||
"sunday": "Zondag",
|
||||
"monday": "Maandag",
|
||||
"saturday": "Zaterdag",
|
||||
"formats": {
|
||||
"mdy": "MM/DD/JJJJ (VS)",
|
||||
"dmy": "DD/MM/JJJJ (Europa)",
|
||||
"ymd": "JJJJ-MM-DD (ISO)",
|
||||
"long": "Lang formaat (bijv. 1 januari 2024)",
|
||||
"short": "Kort formaat (bijv. 1 jan 2024)"
|
||||
}
|
||||
},
|
||||
"sidepanel": {
|
||||
"title": "Zijpaneel Aanpassen",
|
||||
"description": "Pas de indeling en zichtbaarheid van kaarten in het zijpaneel aan. Deze sectie is alleen beschikbaar op grote schermapparaten zoals tablets en desktops."
|
||||
},
|
||||
"theme": {
|
||||
"title": "Themavoorkeuren",
|
||||
"description": "Kies hoe de site er voor u uitziet. Selecteer een enkel thema of synchroniseer met uw systeem en schakel automatisch tussen dag- en nachtthema's.",
|
||||
"themeMode": "Thamamodus",
|
||||
"light": "Licht",
|
||||
"dark": "Donker",
|
||||
"system": "Systeem"
|
||||
},
|
||||
"notifications": {
|
||||
"settingsSaved": "Instellingen succesvol opgeslagen",
|
||||
"settingsSaveFailed": "Opslaan van instellingen mislukt",
|
||||
"invalidWebhook": "Ongeldige webhook URL"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profielinstellingen",
|
||||
"description": "Werk uw weergavenaam en profielfoto bij.",
|
||||
"photoUpdated": "Foto Bijgewerkt",
|
||||
"photoUpdatedMessage": "Uw profielfoto is succesvol bijgewerkt!",
|
||||
"uploadFailed": "Uploaden Mislukt",
|
||||
"uploadFailedMessage": "Het uploaden van uw foto is mislukt. Probeer het opnieuw.",
|
||||
"profileUpdated": "Profiel Bijgewerkt",
|
||||
"profileUpdatedMessage": "Uw profielgegevens zijn succesvol opgeslagen!",
|
||||
"updateFailed": "Bijwerken Mislukt",
|
||||
"updateFailedMessage": "Kan uw profiel niet bijwerken. Controleer uw verbinding en probeer het opnieuw.",
|
||||
"changePhoto": "Foto Wijzigen",
|
||||
"displayName": "Weergavenaam",
|
||||
"displayNamePlaceholder": "Voer uw weergavenaam in",
|
||||
"timezone": "Tijdzone",
|
||||
"timezonePlaceholder": "Selecteer uw tijdzone",
|
||||
"save": "Opslaan",
|
||||
"cancel": "Annuleren"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Instellingen",
|
||||
"subtitle": "Pas uw ervaring aan en beheer uw accountvoorkeuren",
|
||||
"upgrade": {
|
||||
"title": "Upgraden naar Plus",
|
||||
"description": "Ontgrendel krachtige functies om uw productiviteit te verbeteren",
|
||||
"button": "Nu Upgraden",
|
||||
"features": {
|
||||
"richText": "Rijke tekstbeschrijvingen",
|
||||
"notifications": "Taakmeldingen",
|
||||
"apiIntegrations": "API-integraties",
|
||||
"advancedAutomation": "Geavanceerde automatisering"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
"profile": {
|
||||
"title": "Profielinstellingen",
|
||||
"description": "Werk uw profielgegevens, foto, weergavenaam en tijdzonevoorkeuren bij."
|
||||
},
|
||||
"circle": {
|
||||
"title": "Cirkelinstellingen",
|
||||
"description": "Beheer uw cirkel, nodig leden uit en behandel lidmaatschapsverzoeken."
|
||||
},
|
||||
"account": {
|
||||
"title": "Accountinstellingen",
|
||||
"description": "Beheer uw abonnement, wijzig het wachtwoord en opties voor accountverwijdering."
|
||||
},
|
||||
"subaccounts": {
|
||||
"title": "Beheerde Accounts",
|
||||
"description": "Maak subaccounts aan en beheer ze om in te loggen en toegewezen taken te voltooien."
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Meldingen",
|
||||
"description": "Configureer pushmeldingen, e-mailwaarschuwingen en meldingsdoelen voor taken."
|
||||
},
|
||||
"mfa": {
|
||||
"title": "Multi-Factor Authenticatie",
|
||||
"description": "Voeg een extra beveiligingslaag toe met MFA via authenticatie-apps."
|
||||
},
|
||||
"apitokens": {
|
||||
"title": "API-tokens",
|
||||
"description": "Genereer en beheer toegangstokens voor integraties van derden en API-toegang."
|
||||
},
|
||||
"storage": {
|
||||
"title": "Opslaginstellingen",
|
||||
"description": "Maak een back-up van uw gegevens en herstel ze, beheer lokale opslag en synchronisatievoorkeuren."
|
||||
},
|
||||
"sidepanel": {
|
||||
"title": "Zijpaneel Aanpassen",
|
||||
"description": "Pas de indeling en zichtbaarheid van kaarten in de zijpaneelinterface aan."
|
||||
},
|
||||
"theme": {
|
||||
"title": "Themavoorkeuren",
|
||||
"description": "Kies uw voorkeursthema en configureer de instellingen voor donker/licht modus."
|
||||
},
|
||||
"localization": {
|
||||
"title": "Lokalisatie",
|
||||
"description": "Pas de taal, datumnotatie, tijdnotatie en regionale voorkeuren aan."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Geavanceerde Instellingen",
|
||||
"description": "Configureer webhooks, real-time updates en andere geavanceerde functies voor verbeterde productiviteit."
|
||||
},
|
||||
"developer": {
|
||||
"title": "Ontwikkelaarsinstellingen",
|
||||
"description": "Bekijk technische informatie over authenticatietokens, SSE-verbindingen en foutopsporingsgegevens."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,7 @@ export const AVAILABLE_LANGUAGES = [
|
||||
{ code: 'en', name: 'English', nativeName: 'English' },
|
||||
{ code: 'es', name: 'Spanish', nativeName: 'Español' },
|
||||
{ code: 'fr', name: 'French', nativeName: 'Français' },
|
||||
{ code: 'de', name: 'German', nativeName: 'Deutsch' },
|
||||
{ code: 'ar', name: 'Arabic', nativeName: 'العربية' },
|
||||
{ code: 'he', name: 'Hebrew', nativeName: 'עברית' },
|
||||
{ code: 'zh', name: 'Chinese', nativeName: '中文' },
|
||||
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
|
||||
{ code: 'pt', name: 'Portuguese', nativeName: 'Português' },
|
||||
{ code: 'ru', name: 'Russian', nativeName: 'Русский' },
|
||||
{ code: 'nl', name: 'Dutch', nativeName: 'Nederlands' },
|
||||
]
|
||||
|
||||
export const LocalizationProvider = ({ children }) => {
|
||||
@@ -43,7 +37,10 @@ export const LocalizationProvider = ({ children }) => {
|
||||
TIME_FORMATS.HOUR_12,
|
||||
'timeFormat',
|
||||
)
|
||||
const [firstDayOfWeek, setFirstDayOfWeek] = useStickyState(0, 'firstDayOfWeek') // 0 = Sunday, 1 = Monday
|
||||
const [firstDayOfWeek, setFirstDayOfWeek] = useStickyState(
|
||||
0,
|
||||
'firstDayOfWeek',
|
||||
) // 0 = Sunday, 1 = Monday
|
||||
const [language, setLanguage] = useStickyState('en', 'language')
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,11 +77,26 @@ export const LocalizationProvider = ({ children }) => {
|
||||
|
||||
const formatCalendar = date => {
|
||||
if (!date) return ''
|
||||
return moment(date).calendar()
|
||||
return moment(date).calendar(null, {
|
||||
sameDay: `[Today] ${timeFormat}`,
|
||||
nextDay: `[Tomorrow] ${timeFormat}`,
|
||||
nextWeek: `dddd ${timeFormat}`,
|
||||
lastDay: `[Yesterday] ${timeFormat}`,
|
||||
lastWeek: `[Last] dddd ${timeFormat}`,
|
||||
sameElse: `${dateFormat} ${timeFormat}`,
|
||||
})
|
||||
}
|
||||
|
||||
const isRTL = RTL_LANGUAGES.includes(language)
|
||||
|
||||
const fmt = {
|
||||
date: formatDate,
|
||||
dateTime: formatDateTime,
|
||||
time: formatTime,
|
||||
relative: formatRelative,
|
||||
calendar: formatCalendar,
|
||||
}
|
||||
|
||||
const value = {
|
||||
dateFormat,
|
||||
setDateFormat,
|
||||
@@ -95,11 +107,7 @@ export const LocalizationProvider = ({ children }) => {
|
||||
language,
|
||||
setLanguage,
|
||||
isRTL,
|
||||
formatDate,
|
||||
formatDateTime,
|
||||
formatTime,
|
||||
formatRelative,
|
||||
formatCalendar,
|
||||
fmt,
|
||||
availableLanguages: AVAILABLE_LANGUAGES,
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,21 @@ const allMonths = [
|
||||
* @param {Object} chore - The chore object (needed for nextDueDate null check)
|
||||
* @returns {string} The formatted due date text
|
||||
*/
|
||||
export const getDueDateChipText = (nextDueDate, chore) => {
|
||||
export const getDueDateChipText = (nextDueDate, chore, timeFormat = 'h:mm A') => {
|
||||
if (chore?.nextDueDate === null || nextDueDate === null) return 'No Due Date'
|
||||
|
||||
const dueDate = moment(nextDueDate)
|
||||
const diff = moment(nextDueDate).diff(moment(), 'hours')
|
||||
|
||||
const calendarFormat = {
|
||||
sameDay: `[Today] ${timeFormat}`,
|
||||
nextDay: `[Tomorrow] ${timeFormat}`,
|
||||
nextWeek: `dddd ${timeFormat}`,
|
||||
lastDay: `[Yesterday] ${timeFormat}`,
|
||||
lastWeek: `[Last] dddd ${timeFormat}`,
|
||||
sameElse: `MMM D ${timeFormat}`,
|
||||
}
|
||||
|
||||
// if seconds and minutes set to 59, treat as no time (date only)
|
||||
if (dueDate.seconds() === 59 && dueDate.minutes() === 59) {
|
||||
if (diff < 0) {
|
||||
@@ -33,22 +42,22 @@ export const getDueDateChipText = (nextDueDate, chore) => {
|
||||
if (absDiff <= 48) {
|
||||
return (
|
||||
'Overdue ' +
|
||||
moment(nextDueDate).calendar().split(' at ')[0].toLowerCase()
|
||||
moment(nextDueDate).calendar(null, calendarFormat).split(' ')[0].toLowerCase()
|
||||
)
|
||||
}
|
||||
return 'Overdue ' + dueDate.fromNow()
|
||||
}
|
||||
// if due in next 48 hours, show calendar format without time (e.g., "Tomorrow")
|
||||
if (diff < 48 && diff > 0) {
|
||||
return moment(nextDueDate).calendar().split(' at ')[0]
|
||||
return moment(nextDueDate).calendar(null, calendarFormat).split(' ')[0]
|
||||
}
|
||||
// if due date is after 48 hours, show it in format: Due in 3 days
|
||||
return 'Due ' + dueDate.fromNow()
|
||||
}
|
||||
|
||||
// if due in next 48 hours, we should show it in this format: Tomorrow 11:00 AM
|
||||
// if due in next 48 hours, we should show it in this format: Tomorrow 11:00
|
||||
if (diff < 48 && diff > 0) {
|
||||
return moment(nextDueDate).calendar().replace(' at', '')
|
||||
return moment(nextDueDate).calendar(null, calendarFormat)
|
||||
}
|
||||
return 'Due ' + moment(nextDueDate).fromNow()
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ import TimerSplitButton from './TimerSplitButton.jsx'
|
||||
|
||||
const ChoreView = () => {
|
||||
const { t } = useTranslation('chores')
|
||||
const { formatDate, formatDateTime } = useLocalization()
|
||||
const { fmt } = useLocalization()
|
||||
const [chore, setChore] = useState({})
|
||||
const navigate = useNavigate()
|
||||
const [performers, setPerformers] = useState([])
|
||||
@@ -475,7 +475,7 @@ const ChoreView = () => {
|
||||
)}
|
||||
<Chip startDecorator={<CalendarMonth />} size='md' sx={{ mb: 1 }}>
|
||||
{chore.nextDueDate
|
||||
? `${t('choreView.due')} ${formatDateTime(chore.nextDueDate)}`
|
||||
? `${t('choreView.due')} ${fmt.dateTime(chore.nextDueDate)}`
|
||||
: t('choreView.na')}
|
||||
</Chip>
|
||||
<Box
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import moment from 'moment'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import ThingTriggerSection from './ThingTriggerSection'
|
||||
@@ -76,7 +77,7 @@ const DAY_OCCURRENCE_OPTIONS = [
|
||||
{ value: -1, label: 'Last occurrence' },
|
||||
]
|
||||
// Helper function to generate schedule preview text
|
||||
const generateSchedulePreview = metadata => {
|
||||
const generateSchedulePreview = (metadata, formatTimeFn) => {
|
||||
if (!metadata?.days?.length) return ''
|
||||
|
||||
const dayNames = metadata.days
|
||||
@@ -84,7 +85,7 @@ const generateSchedulePreview = metadata => {
|
||||
.join(', ')
|
||||
|
||||
const timeStr = metadata.time
|
||||
? moment(metadata.time).format('h:mm A')
|
||||
? formatTimeFn(metadata.time)
|
||||
: '6:00 PM'
|
||||
|
||||
if (metadata.weekPattern === 'every_week' || !metadata.weekPattern) {
|
||||
@@ -114,6 +115,7 @@ const RepeatOnSections = ({
|
||||
frequencyMetadata,
|
||||
onFrequencyMetadataUpdate,
|
||||
}) => {
|
||||
const { fmt } = useLocalization()
|
||||
// if time on frequencyMetadata is not set, try to set it to the nextDueDate if available,
|
||||
// otherwise set it to 18:00 of the current day
|
||||
useEffect(() => {
|
||||
@@ -400,7 +402,7 @@ const RepeatOnSections = ({
|
||||
{frequencyMetadata?.days?.length > 0 && (
|
||||
<Card mt={2} p={2}>
|
||||
<Typography level='body-sm' color='primary'>
|
||||
{generateSchedulePreview(frequencyMetadata)}
|
||||
{generateSchedulePreview(frequencyMetadata, fmt.time)}
|
||||
</Typography>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
import { Box, Card, Chip, Typography } from '@mui/joy'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
|
||||
const TimePassedCard = ({ chore, handleAction, onShowDetails }) => {
|
||||
const navigate = useNavigate()
|
||||
const { fmt } = useLocalization()
|
||||
const [time, setTime] = useState(0)
|
||||
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||
const [prevStatus, setPrevStatus] = useState(null) // Initialize as null
|
||||
@@ -187,10 +189,7 @@ const TimePassedCard = ({ chore, handleAction, onShowDetails }) => {
|
||||
size='md'
|
||||
startDecorator={<Flag sx={{ fontSize: 14 }} />}
|
||||
>
|
||||
{new Date(chore.startTime).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{fmt.time(chore.startTime)}
|
||||
</Chip>
|
||||
)}
|
||||
|
||||
@@ -202,10 +201,7 @@ const TimePassedCard = ({ chore, handleAction, onShowDetails }) => {
|
||||
size='md'
|
||||
startDecorator={<Schedule sx={{ fontSize: 14 }} />}
|
||||
>
|
||||
{new Date(chore.timerUpdatedAt).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{fmt.time(chore.timerUpdatedAt)}
|
||||
</Chip>
|
||||
)}
|
||||
</>
|
||||
@@ -219,10 +215,7 @@ const TimePassedCard = ({ chore, handleAction, onShowDetails }) => {
|
||||
size='md'
|
||||
startDecorator={<Schedule sx={{ fontSize: 14 }} />}
|
||||
>
|
||||
{new Date(chore.timerUpdatedAt).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{fmt.time(chore.timerUpdatedAt)}
|
||||
</Chip>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries.jsx'
|
||||
import {
|
||||
getDueDateChipColor,
|
||||
@@ -45,6 +46,7 @@ const ChoreCard = ({
|
||||
onSelectionToggle,
|
||||
}) => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { timeFormat } = useLocalization()
|
||||
|
||||
const { impersonatedUser } = useImpersonateUser()
|
||||
|
||||
@@ -95,7 +97,7 @@ const ChoreCard = ({
|
||||
}}
|
||||
color={getDueDateChipColor(chore.nextDueDate, chore)}
|
||||
>
|
||||
{getDueDateChipText(chore.nextDueDate, chore)}
|
||||
{getDueDateChipText(chore.nextDueDate, chore, timeFormat)}
|
||||
</Chip>
|
||||
|
||||
<Chip
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { Box, Checkbox, Chip, IconButton, Typography } from '@mui/joy'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx'
|
||||
import {
|
||||
getDueDateChipColor,
|
||||
@@ -41,6 +42,7 @@ const CompactChoreCard = ({
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { timeFormat } = useLocalization()
|
||||
const { data: circleMembersData } = useCircleMembers()
|
||||
|
||||
const { impersonatedUser } = useImpersonateUser()
|
||||
@@ -395,7 +397,7 @@ const CompactChoreCard = ({
|
||||
ml: 1,
|
||||
}}
|
||||
>
|
||||
{getDueDateChipText(chore.nextDueDate, chore)}
|
||||
{getDueDateChipText(chore.nextDueDate, chore, timeFormat)}
|
||||
</Chip>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Box, Button, Card, Container, Grid, Sheet, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import useConfirmationModal from '../../hooks/useConfirmationModal'
|
||||
import {
|
||||
useChoreHistory,
|
||||
@@ -44,6 +45,7 @@ const ChoreHistory = () => {
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||
const [editHistory, setEditHistory] = useState(null)
|
||||
const { confirmModalConfig, showConfirmation } = useConfirmationModal()
|
||||
const { fmt } = useLocalization()
|
||||
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
|
||||
const [noteViewerConfig, setNoteViewerConfig] = useState({ isOpen: false })
|
||||
const { showSuccess, showError } = useNotification()
|
||||
@@ -367,7 +369,7 @@ const ChoreHistory = () => {
|
||||
onViewNote={notes => {
|
||||
setNoteViewerConfig({
|
||||
isOpen: true,
|
||||
title: `Updated at ${moment(historyEntry.updatedAt).format('LLLL')}`,
|
||||
title: `Updated at ${fmt.dateTime(historyEntry.updatedAt)}`,
|
||||
content: notes,
|
||||
onClose: () => setNoteViewerConfig({ isOpen: false }),
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@mui/icons-material'
|
||||
import { Avatar, Box, Chip, Grid, IconButton, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
||||
|
||||
const getCompletedChip = historyEntry => {
|
||||
@@ -93,6 +94,7 @@ const HistoryCard = ({
|
||||
onToggleActions,
|
||||
onViewNote,
|
||||
}) => {
|
||||
const { fmt } = useLocalization()
|
||||
const performer = performers.find(p => p.userId === historyEntry.completedBy)
|
||||
const assignedTo = performers.find(p => p.userId === historyEntry.assignedTo)
|
||||
|
||||
@@ -191,9 +193,9 @@ const HistoryCard = ({
|
||||
</Typography>
|
||||
|
||||
<Chip size='sm' startDecorator={<EventNote />}>
|
||||
{moment(
|
||||
{fmt.dateTime(
|
||||
historyEntry.performedAt || historyEntry.updatedAt,
|
||||
).format('MMM DD, h:mm A')}
|
||||
)}
|
||||
</Chip>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
||||
@@ -214,7 +216,7 @@ const HistoryCard = ({
|
||||
>
|
||||
{historyEntry.dueDate && (
|
||||
<Chip size='sm' startDecorator={<CalendarMonth />}>
|
||||
{moment(historyEntry.dueDate).format('MMM DD h:mm A')}
|
||||
{fmt.dateTime(historyEntry.dueDate)}
|
||||
</Chip>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocalization } from '../../../contexts/LocalizationContext'
|
||||
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
|
||||
import { useNotification } from '../../../service/NotificationProvider'
|
||||
import {
|
||||
@@ -23,6 +24,7 @@ import ConfirmationModal from './ConfirmationModal'
|
||||
|
||||
const TimerEditModal = ({ isOpen, onClose, choreId, onTimerUpdate }) => {
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
const { fmt } = useLocalization()
|
||||
|
||||
const [timerData, setTimerData] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -632,11 +634,9 @@ const TimerEditModal = ({ isOpen, onClose, choreId, onTimerUpdate }) => {
|
||||
const sessionDate = moment(pause.start).format(
|
||||
'MMM DD',
|
||||
)
|
||||
const startTime = moment(pause.start).format(
|
||||
'HH:mm',
|
||||
)
|
||||
const startTime = fmt.time(pause.start)
|
||||
const endTime = pause.end
|
||||
? moment(pause.end).format('HH:mm')
|
||||
? fmt.time(pause.end)
|
||||
: null
|
||||
|
||||
const realTimeDuration = isOngoing
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import moment from 'moment'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
import {
|
||||
@@ -27,6 +28,7 @@ import SettingsLayout from './SettingsLayout'
|
||||
const APITokenSettings = () => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { showNotification } = useNotification()
|
||||
const { fmt } = useLocalization()
|
||||
const [tokens, setTokens] = useState([])
|
||||
const [isGetTokenNameModalOpen, setIsGetTokenNameModalOpen] = useState(false)
|
||||
const [showTokenId, setShowTokenId] = useState(null)
|
||||
@@ -106,7 +108,7 @@ const APITokenSettings = () => {
|
||||
<Typography level='body-md'>{token.name}</Typography>
|
||||
<Typography level='body-xs'>
|
||||
{moment(token.createdAt).fromNow()}(
|
||||
{moment(token.createdAt).format('lll')})
|
||||
{fmt.dateTime(token.createdAt)})
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import SubscriptionModal from '../../components/SubscriptionModal'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
import { CancelSubscription, UpdatePassword } from '../../utils/Fetcher'
|
||||
@@ -17,6 +18,7 @@ const AccountSettings = () => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const queryClient = useQueryClient()
|
||||
const { showNotification } = useNotification()
|
||||
const { fmt } = useLocalization()
|
||||
|
||||
const [changePasswordModal, setChangePasswordModal] = useState(false)
|
||||
const [subscriptionModal, setSubscriptionModal] = useState(false)
|
||||
@@ -37,13 +39,9 @@ const AccountSettings = () => {
|
||||
|
||||
const getSubscriptionDetails = () => {
|
||||
if (userProfile?.subscription === 'active') {
|
||||
return `You are currently subscribed to the Plus plan. Your subscription will renew on ${moment(
|
||||
userProfile?.expiration,
|
||||
).format('MMM DD, YYYY')}.`
|
||||
return `You are currently subscribed to the Plus plan. Your subscription will renew on ${fmt.date(userProfile?.expiration)}.`
|
||||
} else if (userProfile?.subscription === 'cancelled') {
|
||||
return `You have cancelled your subscription. Your account will be downgraded to the Free plan on ${moment(
|
||||
userProfile?.expiration,
|
||||
).format('MMM DD, YYYY')}.`
|
||||
return `You have cancelled your subscription. Your account will be downgraded to the Free plan on ${fmt.date(userProfile?.expiration)}.`
|
||||
} else {
|
||||
return `You are currently on the Free plan. Upgrade to the Plus plan to unlock more features.`
|
||||
}
|
||||
@@ -54,9 +52,7 @@ const AccountSettings = () => {
|
||||
return `Plus`
|
||||
} else if (userProfile?.subscription === 'cancelled') {
|
||||
if (moment().isBefore(userProfile?.expiration)) {
|
||||
return `Plus(until ${moment(userProfile?.expiration).format(
|
||||
'MMM DD, YYYY',
|
||||
)})`
|
||||
return `Plus(until ${fmt.date(userProfile?.expiration)})`
|
||||
}
|
||||
return `Free`
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
import {
|
||||
@@ -36,6 +37,7 @@ const CircleSettings = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const { showNotification } = useNotification()
|
||||
const navigate = useNavigate()
|
||||
const { fmt } = useLocalization()
|
||||
|
||||
const [userCircles, setUserCircles] = useState([])
|
||||
const [circleMemberRequests, setCircleMemberRequests] = useState([])
|
||||
@@ -221,12 +223,12 @@ const CircleSettings = () => {
|
||||
</Typography>
|
||||
{member.isActive ? (
|
||||
<Typography level='body-sm'>
|
||||
Joined on {moment(member.createdAt).format('MMM DD, YYYY')}
|
||||
Joined on {fmt.date(member.createdAt)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography level='body-sm' color='danger'>
|
||||
Request to join{' '}
|
||||
{moment(member.updatedAt).format('MMM DD, YYYY')}
|
||||
{fmt.date(member.updatedAt)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -365,7 +367,7 @@ const CircleSettings = () => {
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{lastRefresh && (
|
||||
<Typography level='body-sm' color='neutral'>
|
||||
Last updated: {moment(lastRefresh).format('MMM DD, HH:mm')}
|
||||
Last updated: {fmt.dateTime(lastRefresh)}
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
useLocalization,
|
||||
DATE_FORMATS,
|
||||
TIME_FORMATS,
|
||||
useLocalization,
|
||||
} from '@/contexts/LocalizationContext'
|
||||
import { LanguageOutlined } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
ButtonGroup,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
@@ -45,151 +44,150 @@ const LocalizationSettings = () => {
|
||||
]
|
||||
|
||||
return (
|
||||
<SettingsLayout title="Localization">
|
||||
<div className='grid gap-4'>
|
||||
<SettingsLayout title='Localization'>
|
||||
<div className='grid gap-4 py-4'>
|
||||
<Typography level='body-md'>{t('localization.description')}</Typography>
|
||||
|
||||
<Typography level='h3'>{t('localization.language')}</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-md'>
|
||||
{t('localization.description')}
|
||||
{t('localization.languageDescription')}
|
||||
</Typography>
|
||||
|
||||
<Card variant='outlined' sx={{ p: 3, mb: 2 }}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
||||
<LanguageOutlined />
|
||||
<Typography level='title-md'>
|
||||
{t('localization.language')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography level='body-sm' sx={{ mb: 2 }}>
|
||||
{t('localization.languageDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={language}
|
||||
onChange={(_, value) => setLanguage(value)}
|
||||
sx={{ minWidth: 250 }}
|
||||
>
|
||||
{availableLanguages.map(lang => (
|
||||
<Option key={lang.code} value={lang.code}>
|
||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<Typography>{lang.nativeName}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
({lang.name})
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{isRTL && (
|
||||
<FormHelperText>
|
||||
This language uses right-to-left (RTL) text direction
|
||||
</FormHelperText>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={language}
|
||||
onChange={(_, value) => setLanguage(value)}
|
||||
sx={{ maxWidth: '300px' }}
|
||||
>
|
||||
{availableLanguages.map(lang => (
|
||||
<Option key={lang.code} value={lang.code}>
|
||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<Typography>{lang.nativeName}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
({lang.name})
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{isRTL && (
|
||||
<FormHelperText>
|
||||
{t(
|
||||
'localization.rtlNotice',
|
||||
'This language uses right-to-left (RTL) text direction',
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
<Typography level='h3'>{t('localization.dateFormat')}</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-md'>
|
||||
{t('localization.dateFormatDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={dateFormat}
|
||||
onChange={(_, value) => setDateFormat(value)}
|
||||
sx={{ maxWidth: '300px' }}
|
||||
>
|
||||
{dateFormatOptions.map(option => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>{option.label}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(option.value)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Preview: {sampleDate.format(dateFormat)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography level='title-md' sx={{ mb: 1 }}>
|
||||
{t('localization.dateFormat')}
|
||||
</Typography>
|
||||
<Typography level='body-sm' sx={{ mb: 2 }}>
|
||||
{t('localization.dateFormatDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={dateFormat}
|
||||
onChange={(_, value) => setDateFormat(value)}
|
||||
sx={{ minWidth: 250 }}
|
||||
<Typography level='h3'>{t('localization.timeFormat')}</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-md'>
|
||||
{t('localization.timeFormatDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={timeFormat}
|
||||
onChange={(_, value) => setTimeFormat(value)}
|
||||
sx={{ maxWidth: '300px' }}
|
||||
>
|
||||
<Option value={TIME_FORMATS.HOUR_12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{dateFormatOptions.map(option => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', width: '100%', gap: 2 }}>
|
||||
<Typography>{option.label}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(option.value)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Preview: {sampleDate.format(dateFormat)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography level='title-md' sx={{ mb: 1 }}>
|
||||
{t('localization.timeFormat')}
|
||||
</Typography>
|
||||
<Typography level='body-sm' sx={{ mb: 2 }}>
|
||||
{t('localization.timeFormatDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={timeFormat}
|
||||
onChange={(_, value) => setTimeFormat(value)}
|
||||
sx={{ minWidth: 250 }}
|
||||
>
|
||||
<Option value={TIME_FORMATS.HOUR_12}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', width: '100%', gap: 2 }}>
|
||||
<Typography>{t('localization.12hour')}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(TIME_FORMATS.HOUR_12)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
<Option value={TIME_FORMATS.HOUR_24}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', width: '100%', gap: 2 }}>
|
||||
<Typography>{t('localization.24hour')}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(TIME_FORMATS.HOUR_24)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Preview: {sampleDate.format(timeFormat)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
<Box>
|
||||
<Typography level='title-md' sx={{ mb: 1 }}>
|
||||
{t('localization.firstDayOfWeek')}
|
||||
</Typography>
|
||||
<Typography level='body-sm' sx={{ mb: 2 }}>
|
||||
{t('localization.firstDayOfWeekDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 0 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(0)}
|
||||
>
|
||||
{t('localization.sunday')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 1 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(1)}
|
||||
>
|
||||
{t('localization.monday')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 6 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(6)}
|
||||
>
|
||||
{t('localization.saturday')}
|
||||
</Button>
|
||||
<Typography>{t('localization.12hour')}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(TIME_FORMATS.HOUR_12)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Card>
|
||||
</Option>
|
||||
<Option value={TIME_FORMATS.HOUR_24}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>{t('localization.24hour')}</Typography>
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
{sampleDate.format(TIME_FORMATS.HOUR_24)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Preview: {sampleDate.format(timeFormat)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Typography level='h3'>{t('localization.firstDayOfWeek')}</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-md'>
|
||||
{t('localization.firstDayOfWeekDescription')}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<ButtonGroup variant='outlined'>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 0 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(0)}
|
||||
>
|
||||
{t('localization.sunday')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 1 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(1)}
|
||||
>
|
||||
{t('localization.monday')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={firstDayOfWeek === 6 ? 'solid' : 'outlined'}
|
||||
onClick={() => setFirstDayOfWeek(6)}
|
||||
>
|
||||
{t('localization.saturday')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</FormControl>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ const Settings = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const { showNotification } = useNotification()
|
||||
const navigate = useNavigate()
|
||||
const { formatDate } = useLocalization()
|
||||
const { fmt } = useLocalization()
|
||||
|
||||
const [userCircles, setUserCircles] = useState([])
|
||||
const [circleMemberRequests, setCircleMemberRequests] = useState([])
|
||||
@@ -191,11 +191,11 @@ const Settings = () => {
|
||||
|
||||
const getSubscriptionDetails = () => {
|
||||
if (userProfile?.subscription === 'active') {
|
||||
return `You are currently subscribed to the Plus plan. Your subscription will renew on ${formatDate(
|
||||
return `You are currently subscribed to the Plus plan. Your subscription will renew on ${fmt.date(
|
||||
userProfile?.expiration,
|
||||
)}.`
|
||||
} else if (userProfile?.subscription === 'cancelled') {
|
||||
return `You have cancelled your subscription. Your account will be downgraded to the Free plan on ${formatDate(
|
||||
return `You have cancelled your subscription. Your account will be downgraded to the Free plan on ${fmt.date(
|
||||
userProfile?.expiration,
|
||||
)}.`
|
||||
} else {
|
||||
@@ -207,7 +207,7 @@ const Settings = () => {
|
||||
return `Plus`
|
||||
} else if (userProfile?.subscription === 'cancelled') {
|
||||
if (moment().isBefore(userProfile?.expiration)) {
|
||||
return `Plus(until ${formatDate(userProfile?.expiration)})`
|
||||
return `Plus(until ${fmt.date(userProfile?.expiration)})`
|
||||
}
|
||||
return `Free`
|
||||
} else {
|
||||
@@ -340,12 +340,12 @@ const Settings = () => {
|
||||
</Typography>
|
||||
{member.isActive ? (
|
||||
<Typography level='body-sm'>
|
||||
Joined on {moment(member.createdAt).format('MMM DD, YYYY')}
|
||||
Joined on {fmt.date(member.createdAt)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography level='body-sm' color='danger'>
|
||||
Request to join{' '}
|
||||
{moment(member.updatedAt).format('MMM DD, YYYY')}
|
||||
{fmt.date(member.updatedAt)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -486,7 +486,7 @@ const Settings = () => {
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{lastRefresh && (
|
||||
<Typography level='body-sm' color='neutral'>
|
||||
Last updated: {moment(lastRefresh).format('MMM DD, HH:mm')}
|
||||
Last updated: {fmt.dateTime(lastRefresh)}
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Chip,
|
||||
Container,
|
||||
List,
|
||||
ListItem,
|
||||
@@ -107,6 +108,7 @@ const SettingsOverview = () => {
|
||||
title: t('overview.sections.localization.title'),
|
||||
description: t('overview.sections.localization.description'),
|
||||
icon: <Language />,
|
||||
isBeta: true,
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
@@ -246,9 +248,15 @@ const SettingsOverview = () => {
|
||||
}}
|
||||
>
|
||||
<span>• {t('overview.upgrade.features.richText')}</span>
|
||||
<span>• {t('overview.upgrade.features.notifications')}</span>
|
||||
<span>• {t('overview.upgrade.features.apiIntegrations')}</span>
|
||||
<span>• {t('overview.upgrade.features.advancedAutomation')}</span>
|
||||
<span>
|
||||
• {t('overview.upgrade.features.notifications')}
|
||||
</span>
|
||||
<span>
|
||||
• {t('overview.upgrade.features.apiIntegrations')}
|
||||
</span>
|
||||
<span>
|
||||
• {t('overview.upgrade.features.advancedAutomation')}
|
||||
</span>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -316,12 +324,33 @@ const SettingsOverview = () => {
|
||||
</Avatar>
|
||||
</ListItemDecorator>
|
||||
<ListItemContent sx={{ ml: 2 }}>
|
||||
<Typography
|
||||
level='title-md'
|
||||
sx={{ mb: 0.5, fontWeight: 'lg' }}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
{setting.title}
|
||||
</Typography>
|
||||
<Typography level='title-md' sx={{ fontWeight: 'lg' }}>
|
||||
{setting.title}
|
||||
</Typography>
|
||||
{setting.isBeta && (
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='sm'
|
||||
sx={{
|
||||
height: '20px',
|
||||
fontSize: '0.65rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'warning.main',
|
||||
borderColor: 'warning.main',
|
||||
}}
|
||||
>
|
||||
Early Access
|
||||
</Chip>
|
||||
)}
|
||||
</Box>
|
||||
<Typography
|
||||
level='body-sm'
|
||||
color='neutral'
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { useTheme } from '@mui/joy/styles'
|
||||
import moment from 'moment'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import {
|
||||
Line,
|
||||
LineChart,
|
||||
@@ -40,6 +41,7 @@ import LoadingComponent from '../components/Loading'
|
||||
const ThingsHistory = () => {
|
||||
const { id } = useParams()
|
||||
const theme = useTheme()
|
||||
const { fmt } = useLocalization()
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
@@ -294,9 +296,7 @@ const ThingsHistory = () => {
|
||||
tick='false'
|
||||
tickLine='false'
|
||||
axisLine='false'
|
||||
tickFormatter={tick =>
|
||||
moment(tick).format('ddd MM/DD/yyyy HH:mm:ss')
|
||||
}
|
||||
tickFormatter={tick => fmt.dateTime(tick)}
|
||||
/>
|
||||
<YAxis
|
||||
hide='true'
|
||||
@@ -306,9 +306,7 @@ const ThingsHistory = () => {
|
||||
axisLine='false'
|
||||
/>
|
||||
<Tooltip
|
||||
labelFormatter={label =>
|
||||
moment(label).format('ddd MM/DD/yyyy HH:mm:ss')
|
||||
}
|
||||
labelFormatter={label => fmt.dateTime(label)}
|
||||
/>
|
||||
|
||||
<Line
|
||||
@@ -399,7 +397,7 @@ const ThingsHistory = () => {
|
||||
color='primary'
|
||||
startDecorator={<Schedule />}
|
||||
>
|
||||
{moment(history.updatedAt).format('MMM DD, h:mm A')}
|
||||
{fmt.dateTime(history.updatedAt)}
|
||||
</Chip>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import {
|
||||
useChoreTimer,
|
||||
usePauseChore,
|
||||
@@ -52,6 +53,7 @@ import LoadingComponent from '../components/Loading'
|
||||
|
||||
const TimerDetails = () => {
|
||||
const { choreId } = useParams()
|
||||
const { fmt } = useLocalization()
|
||||
const [timerData, setTimerData] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [editingSessions, setEditingSessions] = useState({})
|
||||
@@ -819,14 +821,14 @@ const TimerDetails = () => {
|
||||
level='body-xs'
|
||||
sx={{ color: 'text.tertiary' }}
|
||||
>
|
||||
Started: {moment(timerData.startTime).format('HH:mm')}
|
||||
Started: {fmt.time(timerData.startTime)}
|
||||
</Typography>
|
||||
{timerData.endTime && (
|
||||
<Typography
|
||||
level='body-xs'
|
||||
sx={{ color: 'text.tertiary' }}
|
||||
>
|
||||
Ended: {moment(timerData.endTime).format('HH:mm')}
|
||||
Ended: {fmt.time(timerData.endTime)}
|
||||
</Typography>
|
||||
)}
|
||||
{!timerData.endTime && (
|
||||
@@ -834,7 +836,7 @@ const TimerDetails = () => {
|
||||
level='body-xs'
|
||||
sx={{ color: 'success.500' }}
|
||||
>
|
||||
Now: {moment(currentTime).format('HH:mm')}
|
||||
Now: {fmt.time(currentTime)}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography
|
||||
@@ -925,9 +927,9 @@ const TimerDetails = () => {
|
||||
const sessionDate = moment(pause.start).format(
|
||||
'MMM DD',
|
||||
)
|
||||
const startTime = moment(pause.start).format('HH:mm')
|
||||
const startTime = fmt.time(pause.start)
|
||||
const endTime = pause.end
|
||||
? moment(pause.end).format('HH:mm')
|
||||
? fmt.time(pause.end)
|
||||
: null
|
||||
|
||||
const realTimeDuration = isOngoing
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from '@mui/joy'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useChores, useChoresHistory } from '../../queries/ChoreQueries'
|
||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx'
|
||||
import { ChoresGrouper } from '../../utils/Chores'
|
||||
@@ -119,6 +120,7 @@ const ChoreHistoryItem = ({ time, name, points, status, performer }) => {
|
||||
}
|
||||
|
||||
const ChoreHistoryTimeline = ({ history }) => {
|
||||
const { fmt } = useLocalization()
|
||||
const groupedHistory = groupByDate(history)
|
||||
|
||||
return (
|
||||
@@ -133,12 +135,7 @@ const ChoreHistoryTimeline = ({ history }) => {
|
||||
{Object.entries(groupedHistory).map(([date, items]) => (
|
||||
<Box key={date} sx={{ mb: 4 }}>
|
||||
<Typography level='title-sm' sx={{ mb: 0.5 }}>
|
||||
{new Date(date).toLocaleDateString([], {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
{fmt.date(date)}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Stack spacing={1}>
|
||||
@@ -146,10 +143,7 @@ const ChoreHistoryTimeline = ({ history }) => {
|
||||
<>
|
||||
<ChoreHistoryItem
|
||||
key={record.id}
|
||||
time={new Date(record.performedAt).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
time={fmt.time(record.performedAt)}
|
||||
name={record.choreName}
|
||||
points={record.points}
|
||||
status={record.status}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
||||
|
||||
@@ -15,6 +16,7 @@ const getAssigneeColor = (assignee, userProfile) => {
|
||||
}
|
||||
const CalendarCard = ({ chores }) => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { fmt } = useLocalization()
|
||||
|
||||
const [selectedDate, setSeletedDate] = useState(null)
|
||||
const Navigate = useNavigate()
|
||||
@@ -197,7 +199,7 @@ const CalendarCard = ({ chores }) => {
|
||||
}}
|
||||
>
|
||||
<Typography level='title-md'>
|
||||
{moment(selectedDate).format('MMMM D, YYYY')}
|
||||
{fmt.date(selectedDate)}
|
||||
</Typography>
|
||||
<Chip variant='soft' color='primary' size='md'>
|
||||
{(() => {
|
||||
@@ -291,7 +293,7 @@ const CalendarCard = ({ chores }) => {
|
||||
color: 'neutral.500',
|
||||
}}
|
||||
>
|
||||
{moment(chore.nextDueDate).format('h:mm A')}
|
||||
{fmt.time(chore.nextDueDate)}
|
||||
</Typography>
|
||||
{/* <Typography
|
||||
level='body-xs'
|
||||
|
||||
@@ -14,7 +14,7 @@ const getAssigneeColor = (assignee, userProfile) => {
|
||||
}
|
||||
const CalendarDual = ({ chores, onDateChange }) => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { firstDayOfWeek } = useLocalization()
|
||||
const { firstDayOfWeek, fmt } = useLocalization()
|
||||
const calendarType =
|
||||
firstDayOfWeek === 1 ? 'iso8601' : firstDayOfWeek === 6 ? 'islamic' : 'gregory'
|
||||
|
||||
@@ -100,10 +100,7 @@ const CalendarDual = ({ chores, onDateChange }) => {
|
||||
{isSecondary && (
|
||||
<div className={styles.secondaryCalendarHeader}>
|
||||
<Typography level='title-md' sx={{ textAlign: 'center', mb: 1 }}>
|
||||
{date.toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
{fmt.date(date, 'MMMM YYYY')}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { CompleteSubTask } from '../../utils/Fetcher'
|
||||
@@ -48,6 +49,7 @@ function SortableItem({
|
||||
editMode,
|
||||
performers = [],
|
||||
}) {
|
||||
const { fmt } = useLocalization()
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({
|
||||
id: task.id,
|
||||
@@ -210,7 +212,7 @@ function SortableItem({
|
||||
fontSize: 'sm',
|
||||
}}
|
||||
>
|
||||
{new Date(task.completedAt).toLocaleString()}
|
||||
{fmt.dateTime(task.completedAt)}
|
||||
{performers.find(p => p.userId === task.completedBy) ? (
|
||||
<Chip>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user