feat(settings): add time format preference
This commit is contained in:
@@ -8,7 +8,7 @@ import { api } from '/api.js';
|
||||
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
import { stagger } from '/utils/ux.js';
|
||||
import { t, formatDate as formatPreferredDate, formatTime, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js';
|
||||
import { t, formatDate as formatPreferredDate, formatTime, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid, formatTimeInput, parseTimeInput, timeInputPlaceholder } from '/i18n.js';
|
||||
import { esc, fmtLocation } from '/utils/html.js';
|
||||
import { refresh as refreshReminders } from '/reminders.js';
|
||||
|
||||
@@ -1204,6 +1204,15 @@ function renderCalendarReminderSection(reminder = null, event = null) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function bindTimeInputs(root) {
|
||||
root.querySelectorAll('.js-time-input').forEach((input) => {
|
||||
input.addEventListener('blur', () => {
|
||||
const parsed = parseTimeInput(input.value);
|
||||
if (parsed) input.value = formatTimeInput(parsed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Event-Modal (Erstellen / Bearbeiten)
|
||||
// --------------------------------------------------------
|
||||
@@ -1265,6 +1274,7 @@ function openEventModal({ mode, event = null, date = null, reminder = null }) {
|
||||
if (isEdit && event?.all_day) { timeFields.style.display = 'none'; alldayFields.style.display = ''; }
|
||||
|
||||
bindDateInputs(panel);
|
||||
bindTimeInputs(panel);
|
||||
|
||||
const iconInput = panel.querySelector('#modal-icon');
|
||||
const iconTrigger = panel.querySelector('#modal-icon-trigger');
|
||||
@@ -1455,7 +1465,7 @@ function buildEventModalContent({ mode, event, date, reminder = null }) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-start-time">${t('calendar.startTimeLabel')}</label>
|
||||
<input type="time" class="form-input" id="modal-start-time" value="${startTime}">
|
||||
<input type="text" class="form-input js-time-input" id="modal-start-time" value="${formatTimeInput(startTime)}" placeholder="${timeInputPlaceholder()}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-grid modal-grid--2">
|
||||
@@ -1465,7 +1475,7 @@ function buildEventModalContent({ mode, event, date, reminder = null }) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-end-time">${t('calendar.endTimeLabel')}</label>
|
||||
<input type="time" class="form-input" id="modal-end-time" value="${endTime}">
|
||||
<input type="text" class="form-input js-time-input" id="modal-end-time" value="${formatTimeInput(endTime)}" placeholder="${timeInputPlaceholder()}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1573,9 +1583,15 @@ async function saveEvent(overlay, mode, eventId, existingReminder = null, attach
|
||||
end_datetime = end_datetime || null;
|
||||
} else {
|
||||
const sd = readDateInput(overlay, '#modal-start-date');
|
||||
const st = overlay.querySelector('#modal-start-time').value;
|
||||
const stRaw = overlay.querySelector('#modal-start-time').value;
|
||||
const st = parseTimeInput(stRaw);
|
||||
const ed = readDateInput(overlay, '#modal-end-date');
|
||||
const et = overlay.querySelector('#modal-end-time').value;
|
||||
const etRaw = overlay.querySelector('#modal-end-time').value;
|
||||
const et = parseTimeInput(etRaw);
|
||||
if ((stRaw && !st) || (etRaw && !et)) {
|
||||
window.oikos?.showToast(t('calendar.invalidDate'), 'error');
|
||||
return;
|
||||
}
|
||||
start_datetime = st ? `${sd}T${st}` : sd;
|
||||
end_datetime = ed ? (et ? `${ed}T${et}` : ed) : null;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ export async function render(container, { user }) {
|
||||
let users = [];
|
||||
let googleStatus = { configured: false, connected: false, lastSync: null };
|
||||
let appleStatus = { configured: false, lastSync: null };
|
||||
let prefs = { visible_meal_types: ['breakfast', 'lunch', 'dinner', 'snack'], currency: 'EUR', date_format: 'mdy', app_name: DEFAULT_APP_NAME };
|
||||
let prefs = { visible_meal_types: ['breakfast', 'lunch', 'dinner', 'snack'], currency: 'EUR', date_format: 'mdy', time_format: '24h', app_name: DEFAULT_APP_NAME };
|
||||
let categories = [];
|
||||
let icsSubscriptions = [];
|
||||
let apiTokens = [];
|
||||
@@ -226,6 +226,9 @@ export async function render(container, { user }) {
|
||||
if (prefs.date_format) {
|
||||
try { localStorage.setItem('oikos-date-format', prefs.date_format); } catch (_) {}
|
||||
}
|
||||
if (prefs.time_format) {
|
||||
try { localStorage.setItem('oikos-time-format', prefs.time_format); } catch (_) {}
|
||||
}
|
||||
if (prefs.app_name) {
|
||||
try { localStorage.setItem(APP_NAME_STORAGE_KEY, prefs.app_name); } catch (_) {}
|
||||
}
|
||||
@@ -338,6 +341,11 @@ export async function render(container, { user }) {
|
||||
<option value="dmy"${prefs.date_format === 'dmy' ? ' selected' : ''}>DD/MM/YYYY</option>
|
||||
<option value="ymd"${prefs.date_format === 'ymd' ? ' selected' : ''}>YYYY-MM-DD</option>
|
||||
</select>
|
||||
<label class="form-label" for="time-format-select" style="margin-top:var(--space-3)">${t('settings.timeFormatLabel')}</label>
|
||||
<select class="form-input" id="time-format-select">
|
||||
<option value="24h"${prefs.time_format === '24h' ? ' selected' : ''}>24 ${t('settings.timeFormatHours')}</option>
|
||||
<option value="12h"${prefs.time_format === '12h' ? ' selected' : ''}>AM/PM</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -858,6 +866,20 @@ function bindEvents(container, user, users, categories, icsSubscriptions, apiTok
|
||||
});
|
||||
}
|
||||
|
||||
const timeFormatSelect = container.querySelector('#time-format-select');
|
||||
if (timeFormatSelect) {
|
||||
timeFormatSelect.addEventListener('change', async () => {
|
||||
try {
|
||||
await api.put('/preferences', { time_format: timeFormatSelect.value });
|
||||
try { localStorage.setItem('oikos-time-format', timeFormatSelect.value); } catch (_) {}
|
||||
window.dispatchEvent(new CustomEvent('time-format-changed', { detail: { timeFormat: timeFormatSelect.value } }));
|
||||
window.oikos?.showToast(t('settings.timeFormatSavedToast'), 'success');
|
||||
} catch (err) {
|
||||
window.oikos?.showToast(err.message ?? t('common.errorGeneric'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const appNameForm = container.querySelector('#app-name-form');
|
||||
if (appNameForm) {
|
||||
appNameForm.addEventListener('submit', async (e) => {
|
||||
|
||||
+19
-4
@@ -8,7 +8,7 @@ import { api } from '/api.js';
|
||||
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
|
||||
import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal } from '/components/modal.js';
|
||||
import { stagger, vibrate } from '/utils/ux.js';
|
||||
import { t, formatDate, formatTime, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js';
|
||||
import { t, formatDate, formatTime, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid, formatTimeInput, parseTimeInput, timeInputPlaceholder } from '/i18n.js';
|
||||
import { esc } from '/utils/html.js';
|
||||
import { refresh as refreshReminders } from '/reminders.js';
|
||||
|
||||
@@ -361,8 +361,8 @@ function renderModalContent({ task = null, users = [], reminder = null } = {}) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="label" for="task-due-time">${t('tasks.dueTimeLabel')}</label>
|
||||
<input class="input" type="time" id="task-due-time" name="due_time"
|
||||
value="${task?.due_time ?? ''}">
|
||||
<input class="input js-time-input" type="text" id="task-due-time" name="due_time"
|
||||
value="${formatTimeInput(task?.due_time ?? '')}" placeholder="${timeInputPlaceholder()}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -566,6 +566,12 @@ function openTaskModal({ task = null, users = [], reminder = null } = {}, contai
|
||||
if (parsed) input.value = formatDateInput(parsed);
|
||||
});
|
||||
});
|
||||
panel.querySelectorAll('.js-time-input').forEach((input) => {
|
||||
input.addEventListener('blur', () => {
|
||||
const parsed = parseTimeInput(input.value);
|
||||
if (parsed) input.value = formatTimeInput(parsed);
|
||||
});
|
||||
});
|
||||
|
||||
// Form-Events
|
||||
panel.querySelector('#task-form')
|
||||
@@ -614,11 +620,20 @@ async function handleFormSubmit(e, container) {
|
||||
priority: form.priority.value,
|
||||
category: form.category.value,
|
||||
due_date: dueDate || null,
|
||||
due_time: form.due_time?.value || null,
|
||||
assigned_to: form.assigned_to.value ? Number(form.assigned_to.value) : null,
|
||||
is_recurring: rrule.is_recurring ? 1 : 0,
|
||||
recurrence_rule: rrule.recurrence_rule,
|
||||
};
|
||||
const dueTimeRaw = form.due_time?.value || '';
|
||||
const dueTime = parseTimeInput(dueTimeRaw);
|
||||
if (dueTimeRaw && !dueTime) {
|
||||
errorEl.textContent = t('calendar.invalidDate');
|
||||
errorEl.hidden = false;
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalLabel;
|
||||
return;
|
||||
}
|
||||
body.due_time = dueTime || null;
|
||||
if (form.status) body.status = form.status.value;
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user