Merge branch 'main' of github.com:rafaelfoster/oikos
This commit is contained in:
@@ -105,7 +105,7 @@ function trapFocus(container) {
|
||||
// --------------------------------------------------------
|
||||
|
||||
function serializeForm(container) {
|
||||
const inputs = container.querySelectorAll('input, select, textarea');
|
||||
const inputs = container.querySelectorAll('input:not([type="file"]), select, textarea');
|
||||
return Array.from(inputs).map((el) => `${el.name || el.id}=${el.value}`).join('&');
|
||||
}
|
||||
|
||||
@@ -327,17 +327,33 @@ export async function closeModal({ force = false } = {}) {
|
||||
if (!force) {
|
||||
const panel = activeOverlay.querySelector('.modal-panel');
|
||||
if (panel && isFormDirty(panel)) {
|
||||
const dirtyOverlay = activeOverlay;
|
||||
const dirtySnapshot = _initialFormSnapshot;
|
||||
let confirmed;
|
||||
try {
|
||||
activeOverlay = null;
|
||||
_isClosing = false;
|
||||
confirmed = await confirmModal(t('modal.unsavedChanges'), {
|
||||
danger: false,
|
||||
confirmLabel: t('modal.discardChanges'),
|
||||
});
|
||||
} catch (err) {
|
||||
activeOverlay = dirtyOverlay;
|
||||
_initialFormSnapshot = dirtySnapshot;
|
||||
_isClosing = false;
|
||||
throw err;
|
||||
}
|
||||
if (!confirmed) { _isClosing = false; return; }
|
||||
activeOverlay = dirtyOverlay;
|
||||
_initialFormSnapshot = dirtySnapshot;
|
||||
if (!confirmed) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
if (window.oikos?.setThemeColor) {
|
||||
window.oikos.setThemeColor(OVERLAY_THEME_COLOR, OVERLAY_THEME_COLOR);
|
||||
}
|
||||
_isClosing = false;
|
||||
return;
|
||||
}
|
||||
_isClosing = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "التنقل",
|
||||
"quickActions": "الإجراءات السريعة",
|
||||
"recipes": "الوصفات",
|
||||
"more": "المزيد"
|
||||
"more": "المزيد",
|
||||
"documents": "المستندات"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "لوحة التحكم",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "أنشئ وصفات واربطها بمخطط الوجبات."
|
||||
},
|
||||
"documents": {
|
||||
"title": "المستندات",
|
||||
"addButton": "إضافة مستند",
|
||||
"searchPlaceholder": "البحث في المستندات...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "كل الفئات",
|
||||
"emptyTitle": "لا توجد مستندات بعد",
|
||||
"emptyDescription": "ارفع مستندات العائلة وتحكم في من يمكنه رؤية كل ملف.",
|
||||
"newTitle": "مستند جديد",
|
||||
"editTitle": "إعدادات المستند",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "كل العائلة",
|
||||
"restricted": "أعضاء محددون",
|
||||
"private": "أنا فقط"
|
||||
},
|
||||
"category": {
|
||||
"medical": "طبي",
|
||||
"school": "مدرسة",
|
||||
"identity": "هوية",
|
||||
"insurance": "تأمين",
|
||||
"finance": "مالية",
|
||||
"home": "منزل",
|
||||
"vehicle": "مركبة",
|
||||
"legal": "قانوني",
|
||||
"travel": "سفر",
|
||||
"pets": "حيوانات أليفة",
|
||||
"warranty": "ضمان",
|
||||
"taxes": "ضرائب",
|
||||
"work": "عمل",
|
||||
"other": "أخرى"
|
||||
},
|
||||
"dropzoneTitle": "أفلت الملف هنا أو انقر للاختيار",
|
||||
"dropzoneHint": "اسحب ملفًا إلى هذه المنطقة أو استخدم محدد الملفات.",
|
||||
"selectedFileLabel": "المحدد: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navigation",
|
||||
"quickActions": "Schnellaktionen",
|
||||
"more": "Mehr",
|
||||
"recipes": "Rezepte"
|
||||
"recipes": "Rezepte",
|
||||
"documents": "Dokumente"
|
||||
},
|
||||
"search": {
|
||||
"title": "Suche",
|
||||
@@ -935,5 +936,66 @@
|
||||
"goCal": "Kalender",
|
||||
"goShop": "Einkaufsliste",
|
||||
"goNotes": "Notizen"
|
||||
},
|
||||
"documents": {
|
||||
"title": "Dokumente",
|
||||
"addButton": "Dokument hinzufügen",
|
||||
"searchPlaceholder": "Dokumente suchen...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Alle Kategorien",
|
||||
"emptyTitle": "Noch keine Dokumente",
|
||||
"emptyDescription": "Lade Familiendokumente hoch und steuere, wer jede Datei sehen darf.",
|
||||
"newTitle": "Neues Dokument",
|
||||
"editTitle": "Dokumenteinstellungen",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Ganze Familie",
|
||||
"restricted": "Ausgewählte Mitglieder",
|
||||
"private": "Nur ich"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Medizin",
|
||||
"school": "Schule",
|
||||
"identity": "Identität",
|
||||
"insurance": "Versicherung",
|
||||
"finance": "Finanzen",
|
||||
"home": "Zuhause",
|
||||
"vehicle": "Fahrzeug",
|
||||
"legal": "Rechtliches",
|
||||
"travel": "Reisen",
|
||||
"pets": "Haustiere",
|
||||
"warranty": "Garantie",
|
||||
"taxes": "Steuern",
|
||||
"work": "Arbeit",
|
||||
"other": "Sonstiges"
|
||||
},
|
||||
"dropzoneTitle": "Datei hier ablegen oder klicken",
|
||||
"dropzoneHint": "Ziehe eine Datei in diesen Bereich oder nutze die Dateiauswahl.",
|
||||
"selectedFileLabel": "Ausgewählt: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Πλοήγηση",
|
||||
"quickActions": "Γρήγορες ενέργειες",
|
||||
"recipes": "Συνταγές",
|
||||
"more": "Περισσότερα"
|
||||
"more": "Περισσότερα",
|
||||
"documents": "Έγγραφα"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Επισκόπηση",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Δημιουργήστε συνταγές και συνδέστε τις με τον προγραμματισμό γευμάτων."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Έγγραφα",
|
||||
"addButton": "Προσθήκη εγγράφου",
|
||||
"searchPlaceholder": "Αναζήτηση εγγράφων...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Όλες οι κατηγορίες",
|
||||
"emptyTitle": "Δεν υπάρχουν έγγραφα ακόμα",
|
||||
"emptyDescription": "Ανεβάστε οικογενειακά έγγραφα και ελέγξτε ποιος μπορεί να βλέπει κάθε αρχείο.",
|
||||
"newTitle": "Νέο έγγραφο",
|
||||
"editTitle": "Ρυθμίσεις εγγράφου",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Όλη η οικογένεια",
|
||||
"restricted": "Επιλεγμένα μέλη",
|
||||
"private": "Μόνο εγώ"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Ιατρικά",
|
||||
"school": "Σχολείο",
|
||||
"identity": "Ταυτότητα",
|
||||
"insurance": "Ασφάλιση",
|
||||
"finance": "Οικονομικά",
|
||||
"home": "Σπίτι",
|
||||
"vehicle": "Όχημα",
|
||||
"legal": "Νομικά",
|
||||
"travel": "Ταξίδια",
|
||||
"pets": "Κατοικίδια",
|
||||
"warranty": "Εγγύηση",
|
||||
"taxes": "Φόροι",
|
||||
"work": "Εργασία",
|
||||
"other": "Άλλο"
|
||||
},
|
||||
"dropzoneTitle": "Αφήστε το αρχείο εδώ ή κάντε κλικ για επιλογή",
|
||||
"dropzoneHint": "Σύρετε ένα αρχείο σε αυτήν την περιοχή ή χρησιμοποιήστε τον επιλογέα αρχείων.",
|
||||
"selectedFileLabel": "Επιλέχθηκε: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-1
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navigation",
|
||||
"quickActions": "Quick actions",
|
||||
"recipes": "Recipes",
|
||||
"more": "More"
|
||||
"more": "More",
|
||||
"documents": "Documents"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Overview",
|
||||
@@ -921,5 +922,66 @@
|
||||
"meals": "Plan meals for the week and link recipes.",
|
||||
"birthdays": "Add birthdays — you will receive a reminder in time.",
|
||||
"recipes": "Create recipes and link them to your meal planner."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documents",
|
||||
"addButton": "Add document",
|
||||
"searchPlaceholder": "Search documents...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "All categories",
|
||||
"emptyTitle": "No documents yet",
|
||||
"emptyDescription": "Upload family documents and control who can see each file.",
|
||||
"newTitle": "New document",
|
||||
"editTitle": "Document settings",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Entire family",
|
||||
"restricted": "Selected members",
|
||||
"private": "Only me"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Medical",
|
||||
"school": "School",
|
||||
"identity": "Identity",
|
||||
"insurance": "Insurance",
|
||||
"finance": "Finance",
|
||||
"home": "Home",
|
||||
"vehicle": "Vehicle",
|
||||
"legal": "Legal",
|
||||
"travel": "Travel",
|
||||
"pets": "Pets",
|
||||
"warranty": "Warranty",
|
||||
"taxes": "Taxes",
|
||||
"work": "Work",
|
||||
"other": "Other"
|
||||
},
|
||||
"dropzoneTitle": "Drop file here or click to choose",
|
||||
"dropzoneHint": "Drag a file into this area, or use the file picker.",
|
||||
"selectedFileLabel": "Selected: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navegación",
|
||||
"quickActions": "Acciones rápidas",
|
||||
"recipes": "Recetas",
|
||||
"more": "Más"
|
||||
"more": "Más",
|
||||
"documents": "Documentos"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Inicio",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Crea recetas y vincúlalas con tu planificador de comidas."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documentos",
|
||||
"addButton": "Agregar documento",
|
||||
"searchPlaceholder": "Buscar documentos...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Todas las categorías",
|
||||
"emptyTitle": "Aún no hay documentos",
|
||||
"emptyDescription": "Sube documentos familiares y controla quién puede ver cada archivo.",
|
||||
"newTitle": "Nuevo documento",
|
||||
"editTitle": "Configuración del documento",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Toda la familia",
|
||||
"restricted": "Miembros seleccionados",
|
||||
"private": "Solo yo"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Médico",
|
||||
"school": "Escuela",
|
||||
"identity": "Identidad",
|
||||
"insurance": "Seguro",
|
||||
"finance": "Finanzas",
|
||||
"home": "Hogar",
|
||||
"vehicle": "Vehículo",
|
||||
"legal": "Legal",
|
||||
"travel": "Viajes",
|
||||
"pets": "Mascotas",
|
||||
"warranty": "Garantía",
|
||||
"taxes": "Impuestos",
|
||||
"work": "Trabajo",
|
||||
"other": "Otros"
|
||||
},
|
||||
"dropzoneTitle": "Suelta el archivo aquí o haz clic para elegir",
|
||||
"dropzoneHint": "Arrastra un archivo a esta área o usa el selector de archivos.",
|
||||
"selectedFileLabel": "Seleccionado: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navigation",
|
||||
"quickActions": "Actions rapides",
|
||||
"recipes": "Recettes",
|
||||
"more": "Plus"
|
||||
"more": "Plus",
|
||||
"documents": "Documents"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Accueil",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Créez des recettes et associez-les à votre planification des repas."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documents",
|
||||
"addButton": "Ajouter un document",
|
||||
"searchPlaceholder": "Rechercher des documents...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Toutes les catégories",
|
||||
"emptyTitle": "Aucun document pour le moment",
|
||||
"emptyDescription": "Ajoutez des documents familiaux et contrôlez qui peut voir chaque fichier.",
|
||||
"newTitle": "Nouveau document",
|
||||
"editTitle": "Paramètres du document",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Toute la famille",
|
||||
"restricted": "Membres sélectionnés",
|
||||
"private": "Moi uniquement"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Médical",
|
||||
"school": "École",
|
||||
"identity": "Identité",
|
||||
"insurance": "Assurance",
|
||||
"finance": "Finances",
|
||||
"home": "Maison",
|
||||
"vehicle": "Véhicule",
|
||||
"legal": "Juridique",
|
||||
"travel": "Voyage",
|
||||
"pets": "Animaux",
|
||||
"warranty": "Garantie",
|
||||
"taxes": "Impôts",
|
||||
"work": "Travail",
|
||||
"other": "Autre"
|
||||
},
|
||||
"dropzoneTitle": "Déposez le fichier ici ou cliquez pour choisir",
|
||||
"dropzoneHint": "Glissez un fichier dans cette zone ou utilisez le sélecteur.",
|
||||
"selectedFileLabel": "Sélectionné : {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "नेविगेशन",
|
||||
"quickActions": "त्वरित क्रियाएं",
|
||||
"recipes": "रेसिपी",
|
||||
"more": "और"
|
||||
"more": "और",
|
||||
"documents": "दस्तावेज़"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "डैशबोर्ड",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "रेसिपी बनाएं और उन्हें अपने भोजन योजनाकार से जोड़ें।"
|
||||
},
|
||||
"documents": {
|
||||
"title": "दस्तावेज़",
|
||||
"addButton": "दस्तावेज़ जोड़ें",
|
||||
"searchPlaceholder": "दस्तावेज़ खोजें...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "सभी श्रेणियाँ",
|
||||
"emptyTitle": "अभी कोई दस्तावेज़ नहीं",
|
||||
"emptyDescription": "परिवार के दस्तावेज़ अपलोड करें और तय करें कि हर फ़ाइल कौन देख सकता है।",
|
||||
"newTitle": "नया दस्तावेज़",
|
||||
"editTitle": "दस्तावेज़ सेटिंग्स",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "पूरा परिवार",
|
||||
"restricted": "चुने हुए सदस्य",
|
||||
"private": "केवल मैं"
|
||||
},
|
||||
"category": {
|
||||
"medical": "चिकित्सा",
|
||||
"school": "स्कूल",
|
||||
"identity": "पहचान",
|
||||
"insurance": "बीमा",
|
||||
"finance": "वित्त",
|
||||
"home": "घर",
|
||||
"vehicle": "वाहन",
|
||||
"legal": "कानूनी",
|
||||
"travel": "यात्रा",
|
||||
"pets": "पालतू",
|
||||
"warranty": "वारंटी",
|
||||
"taxes": "कर",
|
||||
"work": "काम",
|
||||
"other": "अन्य"
|
||||
},
|
||||
"dropzoneTitle": "फ़ाइल यहाँ छोड़ें या चुनने के लिए क्लिक करें",
|
||||
"dropzoneHint": "फ़ाइल को इस क्षेत्र में खींचें या फ़ाइल पिकर का उपयोग करें।",
|
||||
"selectedFileLabel": "चयनित: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navigazione",
|
||||
"quickActions": "Azioni rapide",
|
||||
"recipes": "Ricette",
|
||||
"more": "Altro"
|
||||
"more": "Altro",
|
||||
"documents": "Documenti"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Panoramica",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Crea ricette e collegale al tuo piano pasti."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documenti",
|
||||
"addButton": "Aggiungi documento",
|
||||
"searchPlaceholder": "Cerca documenti...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Tutte le categorie",
|
||||
"emptyTitle": "Nessun documento",
|
||||
"emptyDescription": "Carica documenti di famiglia e controlla chi può vedere ogni file.",
|
||||
"newTitle": "Nuovo documento",
|
||||
"editTitle": "Impostazioni documento",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Tutta la famiglia",
|
||||
"restricted": "Membri selezionati",
|
||||
"private": "Solo io"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Medico",
|
||||
"school": "Scuola",
|
||||
"identity": "Identità",
|
||||
"insurance": "Assicurazione",
|
||||
"finance": "Finanze",
|
||||
"home": "Casa",
|
||||
"vehicle": "Veicolo",
|
||||
"legal": "Legale",
|
||||
"travel": "Viaggi",
|
||||
"pets": "Animali",
|
||||
"warranty": "Garanzia",
|
||||
"taxes": "Tasse",
|
||||
"work": "Lavoro",
|
||||
"other": "Altro"
|
||||
},
|
||||
"dropzoneTitle": "Rilascia il file qui o fai clic per scegliere",
|
||||
"dropzoneHint": "Trascina un file in quest’area oppure usa il selettore.",
|
||||
"selectedFileLabel": "Selezionato: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "ナビゲーション",
|
||||
"quickActions": "クイックアクション",
|
||||
"recipes": "レシピ",
|
||||
"more": "もっと見る"
|
||||
"more": "もっと見る",
|
||||
"documents": "書類"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "ダッシュボード",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "レシピを作成して、食事プランに関連付けましょう。"
|
||||
},
|
||||
"documents": {
|
||||
"title": "書類",
|
||||
"addButton": "書類を追加",
|
||||
"searchPlaceholder": "書類を検索...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "すべてのカテゴリ",
|
||||
"emptyTitle": "書類はまだありません",
|
||||
"emptyDescription": "家族の書類をアップロードし、各ファイルを見られるメンバーを管理できます。",
|
||||
"newTitle": "新しい書類",
|
||||
"editTitle": "書類設定",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "家族全員",
|
||||
"restricted": "選択したメンバー",
|
||||
"private": "自分のみ"
|
||||
},
|
||||
"category": {
|
||||
"medical": "医療",
|
||||
"school": "学校",
|
||||
"identity": "本人確認",
|
||||
"insurance": "保険",
|
||||
"finance": "金融",
|
||||
"home": "家",
|
||||
"vehicle": "車両",
|
||||
"legal": "法務",
|
||||
"travel": "旅行",
|
||||
"pets": "ペット",
|
||||
"warranty": "保証",
|
||||
"taxes": "税金",
|
||||
"work": "仕事",
|
||||
"other": "その他"
|
||||
},
|
||||
"dropzoneTitle": "ここにファイルをドロップ、またはクリックして選択",
|
||||
"dropzoneHint": "この領域にファイルをドラッグするか、ファイル選択を使用します。",
|
||||
"selectedFileLabel": "選択済み: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-1
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navegação",
|
||||
"quickActions": "Ações rápidas",
|
||||
"recipes": "Receitas",
|
||||
"more": "Mais"
|
||||
"more": "Mais",
|
||||
"documents": "Documentos"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Painel",
|
||||
@@ -903,5 +904,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Crie receitas e vincule-as ao seu planejador de refeições."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documentos",
|
||||
"addButton": "Adicionar documento",
|
||||
"searchPlaceholder": "Buscar documentos...",
|
||||
"gridView": "Visualizacao em grade",
|
||||
"listView": "Visualizacao em lista",
|
||||
"viewToggle": "Visualizacao de documentos",
|
||||
"allCategories": "Todas as categorias",
|
||||
"emptyTitle": "Nenhum documento ainda",
|
||||
"emptyDescription": "Envie documentos da familia e controle quem pode ver cada arquivo.",
|
||||
"newTitle": "Novo documento",
|
||||
"editTitle": "Configuracoes do documento",
|
||||
"nameLabel": "Nome",
|
||||
"descriptionLabel": "Descricao",
|
||||
"categoryLabel": "Categoria",
|
||||
"fileLabel": "Arquivo",
|
||||
"fileHint": "PDF, imagens, texto e arquivos Office ate 5 MB.",
|
||||
"visibilityLabel": "Visibilidade",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Membros permitidos",
|
||||
"uploadAction": "Enviar",
|
||||
"downloadAction": "Baixar",
|
||||
"editAction": "Configuracoes",
|
||||
"archiveAction": "Arquivar",
|
||||
"restoreAction": "Restaurar",
|
||||
"savedToast": "Documento salvo.",
|
||||
"uploadedToast": "Documento enviado.",
|
||||
"archivedToast": "Documento arquivado.",
|
||||
"restoredToast": "Documento restaurado.",
|
||||
"deletedToast": "Documento excluido.",
|
||||
"deleteConfirm": "Excluir documento \"{{name}}\"?",
|
||||
"fileRequired": "Selecione um arquivo para enviar.",
|
||||
"fileTooLarge": "O arquivo pode ter no maximo 5 MB.",
|
||||
"fileReadError": "Nao foi possivel ler o arquivo.",
|
||||
"statusActive": "Ativo",
|
||||
"statusArchived": "Arquivado",
|
||||
"visibility": {
|
||||
"family": "Familia inteira",
|
||||
"restricted": "Membros selecionados",
|
||||
"private": "Somente eu"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Medico",
|
||||
"school": "Escola",
|
||||
"identity": "Identidade",
|
||||
"insurance": "Seguro",
|
||||
"finance": "Financeiro",
|
||||
"home": "Casa",
|
||||
"vehicle": "Veiculo",
|
||||
"legal": "Juridico",
|
||||
"travel": "Viagem",
|
||||
"pets": "Pets",
|
||||
"warranty": "Garantia",
|
||||
"taxes": "Impostos",
|
||||
"work": "Trabalho",
|
||||
"other": "Outros"
|
||||
},
|
||||
"dropzoneTitle": "Solte o arquivo aqui ou clique para escolher",
|
||||
"dropzoneHint": "Arraste um arquivo para esta area, ou use o seletor de arquivos.",
|
||||
"selectedFileLabel": "Selecionado: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Навигация",
|
||||
"quickActions": "Быстрые действия",
|
||||
"recipes": "Рецепты",
|
||||
"more": "Ещё"
|
||||
"more": "Ещё",
|
||||
"documents": "Документы"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Обзор",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Создавайте рецепты и связывайте их с вашим планом питания."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Документы",
|
||||
"addButton": "Добавить документ",
|
||||
"searchPlaceholder": "Поиск документов...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Все категории",
|
||||
"emptyTitle": "Документов пока нет",
|
||||
"emptyDescription": "Загружайте семейные документы и управляйте доступом к каждому файлу.",
|
||||
"newTitle": "Новый документ",
|
||||
"editTitle": "Настройки документа",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Вся семья",
|
||||
"restricted": "Выбранные участники",
|
||||
"private": "Только я"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Медицина",
|
||||
"school": "Школа",
|
||||
"identity": "Удостоверения",
|
||||
"insurance": "Страхование",
|
||||
"finance": "Финансы",
|
||||
"home": "Дом",
|
||||
"vehicle": "Автомобиль",
|
||||
"legal": "Юридическое",
|
||||
"travel": "Путешествия",
|
||||
"pets": "Питомцы",
|
||||
"warranty": "Гарантия",
|
||||
"taxes": "Налоги",
|
||||
"work": "Работа",
|
||||
"other": "Другое"
|
||||
},
|
||||
"dropzoneTitle": "Перетащите файл сюда или нажмите для выбора",
|
||||
"dropzoneHint": "Перетащите файл в эту область или используйте выбор файла.",
|
||||
"selectedFileLabel": "Выбрано: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Navigering",
|
||||
"quickActions": "Snabba åtgärder",
|
||||
"recipes": "Recept",
|
||||
"more": "Mer"
|
||||
"more": "Mer",
|
||||
"documents": "Dokument"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Översikt",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Skapa recept och koppla dem till din måltidsplanering."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Dokument",
|
||||
"addButton": "Lägg till dokument",
|
||||
"searchPlaceholder": "Sök dokument...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Alla kategorier",
|
||||
"emptyTitle": "Inga dokument ännu",
|
||||
"emptyDescription": "Ladda upp familjedokument och styr vem som kan se varje fil.",
|
||||
"newTitle": "Nytt dokument",
|
||||
"editTitle": "Dokumentinställningar",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Hela familjen",
|
||||
"restricted": "Valda medlemmar",
|
||||
"private": "Endast jag"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Medicinskt",
|
||||
"school": "Skola",
|
||||
"identity": "Identitet",
|
||||
"insurance": "Försäkring",
|
||||
"finance": "Ekonomi",
|
||||
"home": "Hem",
|
||||
"vehicle": "Fordon",
|
||||
"legal": "Juridiskt",
|
||||
"travel": "Resor",
|
||||
"pets": "Husdjur",
|
||||
"warranty": "Garanti",
|
||||
"taxes": "Skatter",
|
||||
"work": "Arbete",
|
||||
"other": "Övrigt"
|
||||
},
|
||||
"dropzoneTitle": "Släpp filen här eller klicka för att välja",
|
||||
"dropzoneHint": "Dra en fil till området eller använd filväljaren.",
|
||||
"selectedFileLabel": "Vald: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Gezinme",
|
||||
"quickActions": "Hızlı işlemler",
|
||||
"recipes": "Tarifler",
|
||||
"more": "Daha Fazla"
|
||||
"more": "Daha Fazla",
|
||||
"documents": "Belgeler"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Genel Bakış",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "Tarifler oluşturun ve yemek planlayıcınıza bağlayın."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Belgeler",
|
||||
"addButton": "Belge ekle",
|
||||
"searchPlaceholder": "Belgelerde ara...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Tüm kategoriler",
|
||||
"emptyTitle": "Henüz belge yok",
|
||||
"emptyDescription": "Aile belgelerini yükleyin ve her dosyayı kimlerin görebileceğini yönetin.",
|
||||
"newTitle": "Yeni belge",
|
||||
"editTitle": "Belge ayarları",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Tüm aile",
|
||||
"restricted": "Seçili üyeler",
|
||||
"private": "Sadece ben"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Tıbbi",
|
||||
"school": "Okul",
|
||||
"identity": "Kimlik",
|
||||
"insurance": "Sigorta",
|
||||
"finance": "Finans",
|
||||
"home": "Ev",
|
||||
"vehicle": "Araç",
|
||||
"legal": "Hukuki",
|
||||
"travel": "Seyahat",
|
||||
"pets": "Evcil hayvanlar",
|
||||
"warranty": "Garanti",
|
||||
"taxes": "Vergiler",
|
||||
"work": "İş",
|
||||
"other": "Diğer"
|
||||
},
|
||||
"dropzoneTitle": "Dosyayı buraya bırakın veya seçmek için tıklayın",
|
||||
"dropzoneHint": "Bir dosyayı bu alana sürükleyin veya dosya seçiciyi kullanın.",
|
||||
"selectedFileLabel": "Seçildi: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "Навігація",
|
||||
"quickActions": "Швидкі дії",
|
||||
"recipes": "Рецепти",
|
||||
"more": "Більше"
|
||||
"more": "Більше",
|
||||
"documents": "Документи"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Огляд",
|
||||
@@ -905,5 +906,66 @@
|
||||
"meals": "Плануйте харчування на тиждень і пов'язуйте рецепти.",
|
||||
"birthdays": "Додайте дні народження — ви отримаєте нагадування завчасно.",
|
||||
"recipes": "Створюйте рецепти та пов'язуйте їх із планувальником харчування."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Документи",
|
||||
"addButton": "Додати документ",
|
||||
"searchPlaceholder": "Пошук документів...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "Усі категорії",
|
||||
"emptyTitle": "Документів ще немає",
|
||||
"emptyDescription": "Завантажуйте сімейні документи та керуйте доступом до кожного файлу.",
|
||||
"newTitle": "Новий документ",
|
||||
"editTitle": "Налаштування документа",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "Уся сім’я",
|
||||
"restricted": "Вибрані учасники",
|
||||
"private": "Лише я"
|
||||
},
|
||||
"category": {
|
||||
"medical": "Медицина",
|
||||
"school": "Школа",
|
||||
"identity": "Посвідчення",
|
||||
"insurance": "Страхування",
|
||||
"finance": "Фінанси",
|
||||
"home": "Дім",
|
||||
"vehicle": "Авто",
|
||||
"legal": "Юридичне",
|
||||
"travel": "Подорожі",
|
||||
"pets": "Тварини",
|
||||
"warranty": "Гарантія",
|
||||
"taxes": "Податки",
|
||||
"work": "Робота",
|
||||
"other": "Інше"
|
||||
},
|
||||
"dropzoneTitle": "Перетягніть файл сюди або натисніть для вибору",
|
||||
"dropzoneHint": "Перетягніть файл у цю область або скористайтеся вибором файлу.",
|
||||
"selectedFileLabel": "Вибрано: {{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-2
@@ -46,7 +46,8 @@
|
||||
"navigation": "导航",
|
||||
"quickActions": "快捷操作",
|
||||
"recipes": "食谱",
|
||||
"more": "更多"
|
||||
"more": "更多",
|
||||
"documents": "文档"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
@@ -897,5 +898,66 @@
|
||||
},
|
||||
"emptyHint": {
|
||||
"recipes": "创建食谱并将其关联到你的膳食计划。"
|
||||
},
|
||||
"documents": {
|
||||
"title": "文档",
|
||||
"addButton": "添加文档",
|
||||
"searchPlaceholder": "搜索文档...",
|
||||
"gridView": "Grid view",
|
||||
"listView": "List view",
|
||||
"viewToggle": "Document view",
|
||||
"allCategories": "所有类别",
|
||||
"emptyTitle": "还没有文档",
|
||||
"emptyDescription": "上传家庭文档并控制每个文件的可见成员。",
|
||||
"newTitle": "新文档",
|
||||
"editTitle": "文档设置",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel": "Description",
|
||||
"categoryLabel": "Category",
|
||||
"fileLabel": "File",
|
||||
"fileHint": "PDF, images, text and Office files up to 5 MB.",
|
||||
"visibilityLabel": "Visibility",
|
||||
"statusLabel": "Status",
|
||||
"allowedMembersLabel": "Allowed members",
|
||||
"uploadAction": "Upload",
|
||||
"downloadAction": "Download",
|
||||
"editAction": "Settings",
|
||||
"archiveAction": "Archive",
|
||||
"restoreAction": "Restore",
|
||||
"savedToast": "Document saved.",
|
||||
"uploadedToast": "Document uploaded.",
|
||||
"archivedToast": "Document archived.",
|
||||
"restoredToast": "Document restored.",
|
||||
"deletedToast": "Document deleted.",
|
||||
"deleteConfirm": "Delete document \"{{name}}\"?",
|
||||
"fileRequired": "Select a file to upload.",
|
||||
"fileTooLarge": "File may be at most 5 MB.",
|
||||
"fileReadError": "File could not be read.",
|
||||
"statusActive": "Active",
|
||||
"statusArchived": "Archived",
|
||||
"visibility": {
|
||||
"family": "整个家庭",
|
||||
"restricted": "选定成员",
|
||||
"private": "仅我"
|
||||
},
|
||||
"category": {
|
||||
"medical": "医疗",
|
||||
"school": "学校",
|
||||
"identity": "身份",
|
||||
"insurance": "保险",
|
||||
"finance": "财务",
|
||||
"home": "家庭",
|
||||
"vehicle": "车辆",
|
||||
"legal": "法律",
|
||||
"travel": "旅行",
|
||||
"pets": "宠物",
|
||||
"warranty": "保修",
|
||||
"taxes": "税务",
|
||||
"work": "工作",
|
||||
"other": "其他"
|
||||
},
|
||||
"dropzoneTitle": "将文件拖到此处或点击选择",
|
||||
"dropzoneHint": "将文件拖入此区域,或使用文件选择器。",
|
||||
"selectedFileLabel": "已选择:{{name}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* Module: Family Documents
|
||||
* Purpose: Grid/list document management with local uploads and member visibility.
|
||||
* Dependencies: /api.js, shared modal, i18n
|
||||
*/
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
import { t, formatDate } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
import { stagger } from '/utils/ux.js';
|
||||
|
||||
const CATEGORIES = ['medical', 'school', 'identity', 'insurance', 'finance', 'home', 'vehicle', 'legal', 'travel', 'pets', 'warranty', 'taxes', 'work', 'other'];
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
const CATEGORY_ICONS = {
|
||||
medical: 'heart-pulse',
|
||||
school: 'graduation-cap',
|
||||
identity: 'badge-check',
|
||||
insurance: 'shield-check',
|
||||
finance: 'landmark',
|
||||
home: 'home',
|
||||
vehicle: 'car',
|
||||
legal: 'scale',
|
||||
travel: 'plane',
|
||||
pets: 'paw-print',
|
||||
warranty: 'receipt',
|
||||
taxes: 'file-spreadsheet',
|
||||
work: 'briefcase-business',
|
||||
other: 'folder',
|
||||
};
|
||||
|
||||
function categoryLabels() {
|
||||
return Object.fromEntries(CATEGORIES.map((category) => [category, t(`documents.category.${category}`)]));
|
||||
}
|
||||
|
||||
let state = {
|
||||
documents: [],
|
||||
members: [],
|
||||
view: localStorage.getItem('oikos-documents-view') || 'grid',
|
||||
status: 'active',
|
||||
category: '',
|
||||
query: '',
|
||||
};
|
||||
let _container = null;
|
||||
|
||||
export async function render(container) {
|
||||
_container = container;
|
||||
container.innerHTML = `
|
||||
<div class="documents-page">
|
||||
<div class="documents-toolbar">
|
||||
<h1 class="documents-toolbar__title">${t('documents.title')}</h1>
|
||||
<div class="documents-toolbar__search">
|
||||
<i data-lucide="search" class="documents-toolbar__search-icon" aria-hidden="true"></i>
|
||||
<input class="documents-toolbar__search-input" id="documents-search" type="search" placeholder="${t('documents.searchPlaceholder')}" autocomplete="off">
|
||||
</div>
|
||||
<div class="documents-view-toggle" role="group" aria-label="${t('documents.viewToggle')}">
|
||||
<button class="documents-view-toggle__btn ${state.view === 'grid' ? 'documents-view-toggle__btn--active' : ''}" data-view="grid" aria-label="${t('documents.gridView')}">
|
||||
<i data-lucide="layout-grid" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="documents-view-toggle__btn ${state.view === 'list' ? 'documents-view-toggle__btn--active' : ''}" data-view="list" aria-label="${t('documents.listView')}">
|
||||
<i data-lucide="list" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn--primary" id="documents-add-btn">
|
||||
<i data-lucide="upload" class="icon-base" aria-hidden="true"></i>
|
||||
${t('documents.addButton')}
|
||||
</button>
|
||||
</div>
|
||||
<div class="documents-filters">
|
||||
<select class="input documents-filter-select" id="documents-status">
|
||||
<option value="active">${t('documents.statusActive')}</option>
|
||||
<option value="archived">${t('documents.statusArchived')}</option>
|
||||
</select>
|
||||
<select class="input documents-filter-select" id="documents-category">
|
||||
<option value="">${t('documents.allCategories')}</option>
|
||||
${CATEGORIES.map((category) => `<option value="${category}">${categoryLabels()[category]}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div id="documents-list" class="documents-list documents-list--${state.view}"></div>
|
||||
<button class="page-fab" id="fab-new-document" aria-label="${t('documents.addButton')}">
|
||||
<i data-lucide="upload" class="icon-2xl" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
|
||||
await Promise.all([loadMembers(), loadDocuments()]);
|
||||
bindPageEvents();
|
||||
renderDocuments();
|
||||
}
|
||||
|
||||
async function loadMembers() {
|
||||
const res = await api.get('/family/members');
|
||||
state.members = res.data || [];
|
||||
}
|
||||
|
||||
async function loadDocuments() {
|
||||
const params = new URLSearchParams();
|
||||
params.set('status', state.status);
|
||||
if (state.category) params.set('category', state.category);
|
||||
const res = await api.get(`/documents?${params.toString()}`);
|
||||
state.documents = res.data || [];
|
||||
}
|
||||
|
||||
function bindPageEvents() {
|
||||
_container.querySelector('#documents-add-btn')?.addEventListener('click', () => openDocumentModal());
|
||||
_container.querySelector('#fab-new-document')?.addEventListener('click', () => openDocumentModal());
|
||||
_container.querySelector('#documents-search')?.addEventListener('input', (e) => {
|
||||
state.query = e.target.value.trim().toLowerCase();
|
||||
renderDocuments();
|
||||
});
|
||||
_container.querySelector('#documents-status')?.addEventListener('change', async (e) => {
|
||||
state.status = e.target.value;
|
||||
await loadDocuments();
|
||||
renderDocuments();
|
||||
});
|
||||
_container.querySelector('#documents-category')?.addEventListener('change', async (e) => {
|
||||
state.category = e.target.value;
|
||||
await loadDocuments();
|
||||
renderDocuments();
|
||||
});
|
||||
_container.querySelector('.documents-view-toggle')?.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-view]');
|
||||
if (!btn) return;
|
||||
state.view = btn.dataset.view;
|
||||
localStorage.setItem('oikos-documents-view', state.view);
|
||||
_container.querySelectorAll('.documents-view-toggle__btn').forEach((el) =>
|
||||
el.classList.toggle('documents-view-toggle__btn--active', el === btn)
|
||||
);
|
||||
renderDocuments();
|
||||
});
|
||||
_container.querySelector('#documents-list')?.addEventListener('click', handleDocumentAction);
|
||||
}
|
||||
|
||||
function filteredDocuments() {
|
||||
if (!state.query) return state.documents;
|
||||
return state.documents.filter((doc) =>
|
||||
doc.name.toLowerCase().includes(state.query) ||
|
||||
(doc.description || '').toLowerCase().includes(state.query) ||
|
||||
doc.original_name.toLowerCase().includes(state.query)
|
||||
);
|
||||
}
|
||||
|
||||
function renderDocuments() {
|
||||
const list = _container.querySelector('#documents-list');
|
||||
if (!list) return;
|
||||
const docs = filteredDocuments();
|
||||
list.className = `documents-list documents-list--${state.view}`;
|
||||
if (!docs.length) {
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i data-lucide="folder-open" class="empty-state__icon" aria-hidden="true"></i>
|
||||
<div class="empty-state__title">${t('documents.emptyTitle')}</div>
|
||||
<div class="empty-state__description">${t('documents.emptyDescription')}</div>
|
||||
</div>
|
||||
`;
|
||||
if (window.lucide) lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
list.innerHTML = docs.map((doc) => state.view === 'list' ? renderListItem(doc) : renderGridCard(doc)).join('');
|
||||
if (window.lucide) lucide.createIcons();
|
||||
stagger(list.querySelectorAll('.document-card, .document-row'));
|
||||
}
|
||||
|
||||
function renderMeta(doc) {
|
||||
const labels = categoryLabels();
|
||||
return `
|
||||
<span><i data-lucide="${CATEGORY_ICONS[doc.category] || 'folder'}" aria-hidden="true"></i>${labels[doc.category] || doc.category}</span>
|
||||
<span><i data-lucide="${doc.visibility === 'family' ? 'users' : doc.visibility === 'private' ? 'lock' : 'user-check'}" aria-hidden="true"></i>${t(`documents.visibility.${doc.visibility}`)}</span>
|
||||
<span>${formatFileSize(doc.file_size)}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderActions(doc) {
|
||||
return `
|
||||
<a class="btn btn--ghost btn--icon btn--icon-sm" href="/api/v1/documents/${doc.id}/download" download title="${t('documents.downloadAction')}" aria-label="${t('documents.downloadAction')}">
|
||||
<i data-lucide="download" class="icon-base" aria-hidden="true"></i>
|
||||
</a>
|
||||
<button class="btn btn--ghost btn--icon btn--icon-sm" data-action="edit" data-id="${doc.id}" title="${t('documents.editAction')}" aria-label="${t('documents.editAction')}">
|
||||
<i data-lucide="settings" class="icon-base" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn--ghost btn--icon btn--icon-sm" data-action="archive" data-id="${doc.id}" data-archived="${doc.status === 'archived'}" title="${doc.status === 'archived' ? t('documents.restoreAction') : t('documents.archiveAction')}" aria-label="${doc.status === 'archived' ? t('documents.restoreAction') : t('documents.archiveAction')}">
|
||||
<i data-lucide="${doc.status === 'archived' ? 'archive-restore' : 'archive'}" class="icon-base" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn--ghost btn--icon btn--icon-sm documents-danger" data-action="delete" data-id="${doc.id}" title="${t('common.delete')}" aria-label="${t('common.delete')}">
|
||||
<i data-lucide="trash-2" class="icon-base" aria-hidden="true"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderGridCard(doc) {
|
||||
return `
|
||||
<article class="document-card" data-id="${doc.id}">
|
||||
<div class="document-card__icon"><i data-lucide="${CATEGORY_ICONS[doc.category] || 'file'}" aria-hidden="true"></i></div>
|
||||
<div class="document-card__body">
|
||||
<h2 class="document-card__title">${esc(doc.name)}</h2>
|
||||
<p class="document-card__description">${esc(doc.description || doc.original_name)}</p>
|
||||
<div class="document-card__meta">${renderMeta(doc)}</div>
|
||||
</div>
|
||||
<div class="document-card__footer">
|
||||
<span>${formatDate(doc.updated_at)}</span>
|
||||
<div class="document-card__actions">${renderActions(doc)}</div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderListItem(doc) {
|
||||
return `
|
||||
<article class="document-row" data-id="${doc.id}">
|
||||
<div class="document-row__icon"><i data-lucide="${CATEGORY_ICONS[doc.category] || 'file'}" aria-hidden="true"></i></div>
|
||||
<div class="document-row__body">
|
||||
<h2 class="document-row__title">${esc(doc.name)}</h2>
|
||||
<div class="document-row__meta">${renderMeta(doc)}</div>
|
||||
</div>
|
||||
<div class="document-row__actions">${renderActions(doc)}</div>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
async function handleDocumentAction(e) {
|
||||
const btn = e.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
const doc = state.documents.find((item) => String(item.id) === String(btn.dataset.id));
|
||||
if (!doc) return;
|
||||
if (btn.dataset.action === 'edit') openDocumentModal(doc);
|
||||
if (btn.dataset.action === 'archive') {
|
||||
await api.patch(`/documents/${doc.id}/archive`, { archived: doc.status !== 'archived' });
|
||||
window.oikos?.showToast(doc.status === 'archived' ? t('documents.restoredToast') : t('documents.archivedToast'), 'success');
|
||||
await loadDocuments();
|
||||
renderDocuments();
|
||||
}
|
||||
if (btn.dataset.action === 'delete') {
|
||||
if (!confirm(t('documents.deleteConfirm', { name: doc.name }))) return;
|
||||
await api.delete(`/documents/${doc.id}`);
|
||||
window.oikos?.showToast(t('documents.deletedToast'), 'success');
|
||||
await loadDocuments();
|
||||
renderDocuments();
|
||||
}
|
||||
}
|
||||
|
||||
function memberOptions(selected = []) {
|
||||
const selectedSet = new Set(selected.map(String));
|
||||
return state.members.map((member) => `
|
||||
<label class="document-member-option">
|
||||
<input type="checkbox" value="${member.id}" ${selectedSet.has(String(member.id)) ? 'checked' : ''}>
|
||||
<span>${esc(member.display_name)}</span>
|
||||
</label>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function openDocumentModal(doc = null) {
|
||||
const isEdit = !!doc;
|
||||
openSharedModal({
|
||||
title: isEdit ? t('documents.editTitle') : t('documents.newTitle'),
|
||||
size: 'lg',
|
||||
content: `
|
||||
<form id="document-form" class="document-form">
|
||||
<div class="modal-grid modal-grid--2">
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-name">${t('documents.nameLabel')}</label>
|
||||
<input class="input" id="document-name" name="name" required maxlength="200" value="${esc(doc?.name || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-category">${t('documents.categoryLabel')}</label>
|
||||
<select class="input" id="document-category">
|
||||
${CATEGORIES.map((category) => `<option value="${category}" ${doc?.category === category ? 'selected' : ''}>${categoryLabels()[category]}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-description">${t('documents.descriptionLabel')}</label>
|
||||
<textarea class="input" id="document-description" rows="3" maxlength="5000">${esc(doc?.description || '')}</textarea>
|
||||
</div>
|
||||
${!isEdit ? `
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-file">${t('documents.fileLabel')}</label>
|
||||
<label class="document-dropzone" id="document-dropzone" for="document-file">
|
||||
<input class="sr-only" id="document-file" type="file" required>
|
||||
<span class="document-dropzone__icon">
|
||||
<i data-lucide="file-up" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="document-dropzone__title">${t('documents.dropzoneTitle')}</span>
|
||||
<span class="document-dropzone__hint">${t('documents.dropzoneHint')}</span>
|
||||
<span class="document-dropzone__file" id="document-selected-file" hidden></span>
|
||||
</label>
|
||||
<p class="document-form__hint">${t('documents.fileHint')}</p>
|
||||
</div>` : ''}
|
||||
<div class="modal-grid modal-grid--2">
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-visibility">${t('documents.visibilityLabel')}</label>
|
||||
<select class="input" id="document-visibility">
|
||||
<option value="family" ${doc?.visibility === 'family' ? 'selected' : ''}>${t('documents.visibility.family')}</option>
|
||||
<option value="restricted" ${doc?.visibility === 'restricted' ? 'selected' : ''}>${t('documents.visibility.restricted')}</option>
|
||||
<option value="private" ${doc?.visibility === 'private' ? 'selected' : ''}>${t('documents.visibility.private')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-status">${t('documents.statusLabel')}</label>
|
||||
<select class="input" id="document-status">
|
||||
<option value="active" ${doc?.status !== 'archived' ? 'selected' : ''}>${t('documents.statusActive')}</option>
|
||||
<option value="archived" ${doc?.status === 'archived' ? 'selected' : ''}>${t('documents.statusArchived')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="document-member-picker" id="document-member-picker">
|
||||
<div class="label">${t('documents.allowedMembersLabel')}</div>
|
||||
<div class="document-member-picker__grid">${memberOptions(doc?.allowed_member_ids || [])}</div>
|
||||
</div>
|
||||
<div id="document-error" class="login-error" hidden></div>
|
||||
<div class="modal-panel__footer" style="padding:0;border:none;margin-top:var(--space-5)">
|
||||
<button type="submit" class="btn btn--primary" id="document-submit">${isEdit ? t('common.save') : t('documents.uploadAction')}</button>
|
||||
</div>
|
||||
</form>
|
||||
`,
|
||||
onSave(panel) {
|
||||
const form = panel.querySelector('#document-form');
|
||||
const visibility = panel.querySelector('#document-visibility');
|
||||
const picker = panel.querySelector('#document-member-picker');
|
||||
const syncVisibility = () => { picker.hidden = visibility.value !== 'restricted'; };
|
||||
visibility.addEventListener('change', syncVisibility);
|
||||
syncVisibility();
|
||||
bindDropzone(panel);
|
||||
form.addEventListener('submit', (event) => saveDocument(event, doc));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function bindDropzone(panel) {
|
||||
const dropzone = panel.querySelector('#document-dropzone');
|
||||
const input = panel.querySelector('#document-file');
|
||||
const selected = panel.querySelector('#document-selected-file');
|
||||
if (!dropzone || !input || !selected) return;
|
||||
|
||||
const syncSelectedFile = () => {
|
||||
const file = input.files?.[0];
|
||||
selected.hidden = !file;
|
||||
selected.textContent = file ? t('documents.selectedFileLabel', { name: file.name }) : '';
|
||||
};
|
||||
|
||||
input.addEventListener('change', syncSelectedFile);
|
||||
['dragenter', 'dragover'].forEach((eventName) => {
|
||||
dropzone.addEventListener(eventName, (event) => {
|
||||
event.preventDefault();
|
||||
dropzone.classList.add('document-dropzone--active');
|
||||
});
|
||||
});
|
||||
['dragleave', 'drop'].forEach((eventName) => {
|
||||
dropzone.addEventListener(eventName, (event) => {
|
||||
event.preventDefault();
|
||||
dropzone.classList.remove('document-dropzone--active');
|
||||
});
|
||||
});
|
||||
dropzone.addEventListener('drop', (event) => {
|
||||
const file = event.dataTransfer?.files?.[0];
|
||||
if (!file) return;
|
||||
const transfer = new DataTransfer();
|
||||
transfer.items.add(file);
|
||||
input.files = transfer.files;
|
||||
syncSelectedFile();
|
||||
});
|
||||
}
|
||||
|
||||
async function saveDocument(event, doc) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
const error = form.querySelector('#document-error');
|
||||
const submit = form.querySelector('#document-submit');
|
||||
error.hidden = true;
|
||||
submit.disabled = true;
|
||||
try {
|
||||
const visibility = form.querySelector('#document-visibility').value;
|
||||
const payload = {
|
||||
name: form.querySelector('#document-name').value.trim(),
|
||||
description: form.querySelector('#document-description').value.trim() || null,
|
||||
category: form.querySelector('#document-category').value,
|
||||
visibility,
|
||||
status: form.querySelector('#document-status').value,
|
||||
allowed_member_ids: visibility === 'restricted'
|
||||
? Array.from(form.querySelectorAll('.document-member-picker input:checked')).map((input) => Number(input.value))
|
||||
: [],
|
||||
};
|
||||
if (!doc) {
|
||||
const file = form.querySelector('#document-file').files?.[0];
|
||||
if (!file) throw new Error(t('documents.fileRequired'));
|
||||
if (file.size > MAX_FILE_SIZE) throw new Error(t('documents.fileTooLarge'));
|
||||
payload.original_name = file.name;
|
||||
payload.content_data = await readFileAsDataUrl(file);
|
||||
if (!payload.name) payload.name = file.name.replace(/\.[^.]+$/, '');
|
||||
}
|
||||
if (!payload.name) throw new Error(t('common.required'));
|
||||
if (doc) await api.put(`/documents/${doc.id}`, payload);
|
||||
else await api.post('/documents', payload);
|
||||
window.oikos?.showToast(doc ? t('documents.savedToast') : t('documents.uploadedToast'), 'success');
|
||||
closeModal({ force: true });
|
||||
await loadDocuments();
|
||||
renderDocuments();
|
||||
} catch (err) {
|
||||
error.textContent = err.message;
|
||||
error.hidden = false;
|
||||
} finally {
|
||||
submit.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function readFileAsDataUrl(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = () => reject(new Error(t('documents.fileReadError')));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes) return '0 KB';
|
||||
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
+4
-1
@@ -25,6 +25,7 @@ const ROUTES = [
|
||||
{ path: '/recipes', page: '/pages/recipes.js', requiresAuth: true, module: 'recipes' },
|
||||
{ path: '/contacts', page: '/pages/contacts.js', requiresAuth: true, module: 'contacts' },
|
||||
{ path: '/budget', page: '/pages/budget.js', requiresAuth: true, module: 'budget' },
|
||||
{ path: '/documents', page: '/pages/documents.js', requiresAuth: true, module: 'documents' },
|
||||
{ path: '/settings', page: '/pages/settings.js', requiresAuth: true, module: 'settings' },
|
||||
];
|
||||
|
||||
@@ -128,7 +129,7 @@ let _pendingLoginRedirect = false;
|
||||
// --------------------------------------------------------
|
||||
|
||||
const ROUTE_ORDER = ['/', '/tasks', '/calendar', '/birthdays', '/meals', '/recipes', '/shopping',
|
||||
'/notes', '/contacts', '/budget', '/settings'];
|
||||
'/notes', '/contacts', '/budget', '/documents', '/settings'];
|
||||
|
||||
const PRIMARY_NAV = 4;
|
||||
|
||||
@@ -181,6 +182,7 @@ function routeTitle(path) {
|
||||
'/notes': t('nav.notes'),
|
||||
'/contacts': t('nav.contacts'),
|
||||
'/budget': t('nav.budget'),
|
||||
'/documents': t('nav.documents'),
|
||||
'/settings': t('nav.settings'),
|
||||
};
|
||||
return map[path] || getAppName();
|
||||
@@ -886,6 +888,7 @@ function navItems() {
|
||||
{ path: '/notes', label: t('nav.notes'), icon: 'sticky-note' },
|
||||
{ path: '/contacts', label: t('nav.contacts'), icon: 'book-user' },
|
||||
{ path: '/budget', label: t('nav.budget'), icon: 'wallet' },
|
||||
{ path: '/documents', label: t('nav.documents'), icon: 'folder-lock' },
|
||||
{ path: '/settings', label: t('nav.settings'), icon: 'settings' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Module: Family Documents
|
||||
* Purpose: Documents page layout, cards, list rows and upload modal controls.
|
||||
*/
|
||||
|
||||
.documents-page {
|
||||
--module-accent: var(--module-documents);
|
||||
max-width: var(--content-max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.documents-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-top: 3px solid var(--module-accent);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background-color: var(--color-surface);
|
||||
}
|
||||
|
||||
.documents-toolbar__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.documents-toolbar__search {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.documents-toolbar__search-icon {
|
||||
position: absolute;
|
||||
left: var(--space-4);
|
||||
top: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-text-tertiary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.documents-toolbar__search-input {
|
||||
width: 100%;
|
||||
min-height: var(--target-base);
|
||||
padding: 0 var(--space-3) 0 calc(var(--space-10) + var(--space-1));
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.documents-view-toggle {
|
||||
display: inline-flex;
|
||||
padding: 2px;
|
||||
gap: 2px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-2);
|
||||
}
|
||||
|
||||
.documents-view-toggle__btn {
|
||||
width: var(--target-base);
|
||||
height: var(--target-base);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: calc(var(--radius-md) - 2px);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.documents-view-toggle__btn svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.documents-view-toggle__btn--active {
|
||||
color: var(--module-accent);
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.documents-filters {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.documents-filter-select {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.documents-list {
|
||||
padding: 0 var(--space-4) calc(var(--nav-bottom-height) + var(--space-8));
|
||||
}
|
||||
|
||||
.documents-list--grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.documents-list--list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.document-card,
|
||||
.document-row {
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.document-card {
|
||||
min-height: 230px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.document-card__icon,
|
||||
.document-row__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--module-accent);
|
||||
background: color-mix(in srgb, var(--module-accent) 12%, transparent);
|
||||
}
|
||||
|
||||
.document-card__icon svg,
|
||||
.document-row__icon svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.document-card__body {
|
||||
flex: 1;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.document-card__title,
|
||||
.document-row__title {
|
||||
margin: 0;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.document-card__description {
|
||||
min-height: 42px;
|
||||
margin: var(--space-2) 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.document-card__meta,
|
||||
.document-row__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-3);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.document-card__meta span,
|
||||
.document-row__meta span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.document-card__meta svg,
|
||||
.document-row__meta svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.document-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-4);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.document-card__actions,
|
||||
.document-row__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.document-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
|
||||
.document-row__body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.documents-danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.document-form__hint {
|
||||
margin-top: var(--space-1);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.document-dropzone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
min-height: 148px;
|
||||
padding: var(--space-5);
|
||||
border: 1.5px dashed color-mix(in srgb, var(--module-accent) 48%, var(--color-border));
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--module-accent) 7%, var(--color-surface));
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.document-dropzone:hover,
|
||||
.document-dropzone--active {
|
||||
border-color: var(--module-accent);
|
||||
background: color-mix(in srgb, var(--module-accent) 12%, var(--color-surface));
|
||||
}
|
||||
|
||||
.document-dropzone--active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.document-dropzone__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--module-accent);
|
||||
background: var(--color-surface);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.document-dropzone__icon svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.document-dropzone__title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.document-dropzone__hint,
|
||||
.document-dropzone__file {
|
||||
max-width: 100%;
|
||||
font-size: var(--text-xs);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.document-dropzone__file {
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--module-accent);
|
||||
background: color-mix(in srgb, var(--module-accent) 14%, transparent);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.document-member-picker {
|
||||
margin-top: var(--space-2);
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-3);
|
||||
background: var(--color-surface-2);
|
||||
}
|
||||
|
||||
.document-member-picker__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.document-member-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--target-sm);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.documents-toolbar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.documents-toolbar__title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.documents-toolbar__search {
|
||||
order: 4;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.documents-filters {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.documents-filter-select {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.document-row {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.document-row__actions {
|
||||
grid-column: 1 / -1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,8 @@
|
||||
--module-birthdays: var(--_module-birthdays); /* Rose - Geburtstage */
|
||||
--_module-budget: #0F766E;
|
||||
--module-budget: var(--_module-budget); /* Teal-700 - Finanzen, Stabilität */
|
||||
--_module-documents: #1D4ED8;
|
||||
--module-documents: var(--_module-documents); /* Blue - secure family documents */
|
||||
--_module-settings: #6E7781;
|
||||
--module-settings: var(--_module-settings); /* Grau - Konfiguration */
|
||||
--_module-reminders: #0E7490;
|
||||
|
||||
+6
-4
@@ -13,10 +13,10 @@
|
||||
* → bypassCacheUntil (in-memory + Cache API für SW-Restart-Robustheit)
|
||||
*/
|
||||
|
||||
const SHELL_CACHE = 'oikos-shell-v66';
|
||||
const PAGES_CACHE = 'oikos-pages-v61';
|
||||
const LOCALES_CACHE = 'oikos-locales-v10';
|
||||
const ASSETS_CACHE = 'oikos-assets-v61';
|
||||
const SHELL_CACHE = 'oikos-shell-v68';
|
||||
const PAGES_CACHE = 'oikos-pages-v63';
|
||||
const LOCALES_CACHE = 'oikos-locales-v12';
|
||||
const ASSETS_CACHE = 'oikos-assets-v63';
|
||||
const BYPASS_CACHE = 'oikos-bypass-flag';
|
||||
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, LOCALES_CACHE, ASSETS_CACHE];
|
||||
|
||||
@@ -47,6 +47,7 @@ const APP_SHELL = [
|
||||
'/styles/contacts.css',
|
||||
'/styles/birthdays.css',
|
||||
'/styles/budget.css',
|
||||
'/styles/documents.css',
|
||||
'/styles/settings.css',
|
||||
'/styles/recipes.css',
|
||||
'/components/oikos-install-prompt.js',
|
||||
@@ -90,6 +91,7 @@ const PAGE_MODULES = [
|
||||
'/pages/contacts.js',
|
||||
'/pages/birthdays.js',
|
||||
'/pages/budget.js',
|
||||
'/pages/documents.js',
|
||||
'/pages/settings.js',
|
||||
'/pages/login.js',
|
||||
'/pages/recipes.js',
|
||||
|
||||
Reference in New Issue
Block a user