fix(documents): improve upload modal and document theme
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "ضرائب",
|
||||
"work": "عمل",
|
||||
"other": "أخرى"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "أفلت الملف هنا أو انقر للاختيار",
|
||||
"dropzoneHint": "اسحب ملفًا إلى هذه المنطقة أو استخدم محدد الملفات.",
|
||||
"selectedFileLabel": "المحدد: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,6 +993,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "Φόροι",
|
||||
"work": "Εργασία",
|
||||
"other": "Άλλο"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "Αφήστε το αρχείο εδώ ή κάντε κλικ για επιλογή",
|
||||
"dropzoneHint": "Σύρετε ένα αρχείο σε αυτήν την περιοχή ή χρησιμοποιήστε τον επιλογέα αρχείων.",
|
||||
"selectedFileLabel": "Επιλέχθηκε: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,6 +979,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "कर",
|
||||
"work": "काम",
|
||||
"other": "अन्य"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "फ़ाइल यहाँ छोड़ें या चुनने के लिए क्लिक करें",
|
||||
"dropzoneHint": "फ़ाइल को इस क्षेत्र में खींचें या फ़ाइल पिकर का उपयोग करें।",
|
||||
"selectedFileLabel": "चयनित: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "税金",
|
||||
"work": "仕事",
|
||||
"other": "その他"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "ここにファイルをドロップ、またはクリックして選択",
|
||||
"dropzoneHint": "この領域にファイルをドラッグするか、ファイル選択を使用します。",
|
||||
"selectedFileLabel": "選択済み: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,6 +961,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "Налоги",
|
||||
"work": "Работа",
|
||||
"other": "Другое"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "Перетащите файл сюда или нажмите для выбора",
|
||||
"dropzoneHint": "Перетащите файл в эту область или используйте выбор файла.",
|
||||
"selectedFileLabel": "Выбрано: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,6 +963,9 @@
|
||||
"taxes": "Податки",
|
||||
"work": "Робота",
|
||||
"other": "Інше"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "Перетягніть файл сюди або натисніть для вибору",
|
||||
"dropzoneHint": "Перетягніть файл у цю область або скористайтеся вибором файлу.",
|
||||
"selectedFileLabel": "Вибрано: {{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,9 @@
|
||||
"taxes": "税务",
|
||||
"work": "工作",
|
||||
"other": "其他"
|
||||
}
|
||||
},
|
||||
"dropzoneTitle": "将文件拖到此处或点击选择",
|
||||
"dropzoneHint": "将文件拖入此区域,或使用文件选择器。",
|
||||
"selectedFileLabel": "已选择:{{name}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,15 @@ function openDocumentModal(doc = null) {
|
||||
${!isEdit ? `
|
||||
<div class="form-group">
|
||||
<label class="label" for="document-file">${t('documents.fileLabel')}</label>
|
||||
<input class="input" id="document-file" type="file" required>
|
||||
<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">
|
||||
@@ -314,11 +322,47 @@ function openDocumentModal(doc = null) {
|
||||
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;
|
||||
|
||||
@@ -33,18 +33,19 @@
|
||||
|
||||
.documents-toolbar__search-icon {
|
||||
position: absolute;
|
||||
left: var(--space-3);
|
||||
left: var(--space-4);
|
||||
top: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
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 var(--space-9);
|
||||
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);
|
||||
@@ -223,7 +224,73 @@
|
||||
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);
|
||||
|
||||
@@ -172,8 +172,8 @@
|
||||
--module-birthdays: var(--_module-birthdays); /* Rose - Geburtstage */
|
||||
--_module-budget: #0F766E;
|
||||
--module-budget: var(--_module-budget); /* Teal-700 - Finanzen, Stabilität */
|
||||
--_module-documents: #475569;
|
||||
--module-documents: var(--_module-documents); /* Slate - private family records */
|
||||
--_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;
|
||||
|
||||
+4
-4
@@ -13,10 +13,10 @@
|
||||
* → bypassCacheUntil (in-memory + Cache API für SW-Restart-Robustheit)
|
||||
*/
|
||||
|
||||
const SHELL_CACHE = 'oikos-shell-v67';
|
||||
const PAGES_CACHE = 'oikos-pages-v62';
|
||||
const LOCALES_CACHE = 'oikos-locales-v11';
|
||||
const ASSETS_CACHE = 'oikos-assets-v62';
|
||||
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];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user