fix(documents): improve upload modal and document theme

This commit is contained in:
Rafael Foster
2026-04-29 06:27:37 -03:00
parent 72fca92066
commit 1ca8110d56
20 changed files with 200 additions and 28 deletions
+18 -2
View File
@@ -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;
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "ضرائب",
"work": "عمل",
"other": "أخرى"
}
},
"dropzoneTitle": "أفلت الملف هنا أو انقر للاختيار",
"dropzoneHint": "اسحب ملفًا إلى هذه المنطقة أو استخدم محدد الملفات.",
"selectedFileLabel": "المحدد: {{name}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "Φόροι",
"work": "Εργασία",
"other": "Άλλο"
}
},
"dropzoneTitle": "Αφήστε το αρχείο εδώ ή κάντε κλικ για επιλογή",
"dropzoneHint": "Σύρετε ένα αρχείο σε αυτήν την περιοχή ή χρησιμοποιήστε τον επιλογέα αρχείων.",
"selectedFileLabel": "Επιλέχθηκε: {{name}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "कर",
"work": "काम",
"other": "अन्य"
}
},
"dropzoneTitle": "फ़ाइल यहाँ छोड़ें या चुनने के लिए क्लिक करें",
"dropzoneHint": "फ़ाइल को इस क्षेत्र में खींचें या फ़ाइल पिकर का उपयोग करें।",
"selectedFileLabel": "चयनित: {{name}}"
}
}
+4 -1
View File
@@ -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 questarea oppure usa il selettore.",
"selectedFileLabel": "Selezionato: {{name}}"
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "税金",
"work": "仕事",
"other": "その他"
}
},
"dropzoneTitle": "ここにファイルをドロップ、またはクリックして選択",
"dropzoneHint": "この領域にファイルをドラッグするか、ファイル選択を使用します。",
"selectedFileLabel": "選択済み: {{name}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "Налоги",
"work": "Работа",
"other": "Другое"
}
},
"dropzoneTitle": "Перетащите файл сюда или нажмите для выбора",
"dropzoneHint": "Перетащите файл в эту область или используйте выбор файла.",
"selectedFileLabel": "Выбрано: {{name}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -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}}"
}
}
+4 -1
View File
@@ -963,6 +963,9 @@
"taxes": "Податки",
"work": "Робота",
"other": "Інше"
}
},
"dropzoneTitle": "Перетягніть файл сюди або натисніть для вибору",
"dropzoneHint": "Перетягніть файл у цю область або скористайтеся вибором файлу.",
"selectedFileLabel": "Вибрано: {{name}}"
}
}
+4 -1
View File
@@ -955,6 +955,9 @@
"taxes": "税务",
"work": "工作",
"other": "其他"
}
},
"dropzoneTitle": "将文件拖到此处或点击选择",
"dropzoneHint": "将文件拖入此区域,或使用文件选择器。",
"selectedFileLabel": "已选择:{{name}}"
}
}
+45 -1
View File
@@ -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;
+71 -4
View File
@@ -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);
+2 -2
View File
@@ -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
View File
@@ -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];