fix crawling + correct date/time format

This commit is contained in:
2026-10-07 16:01:15 +02:00
parent 9d8c2a0488
commit 25f323ff49
10 changed files with 183 additions and 65 deletions
+1
View File
@@ -36,3 +36,4 @@ td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.weekdays { display: flex; gap: 6px; }
.hidden { display: none !important; }
pre { white-space: pre-wrap; font-size: 12px; max-height: 300px; overflow: auto; }
.invalid { border-color: var(--err); }
+8 -8
View File
@@ -20,7 +20,7 @@
<div class="panel">
<h2>1. Abacus hours</h2>
<p class="muted" id="extract-info">Open MyAbacus, then click the toolbar icon → “Extract hours from this tab”. Or paste below.</p>
<textarea id="days" rows="10" placeholder="2026-10-05 08:00-12:00 13:00-17:24&#10;2026-10-06 8:24"></textarea>
<textarea id="days" rows="10" placeholder="05/10/2026 08:00-12:00 13:00-17:24&#10;06/10/2026 8:24"></textarea>
<div class="row" style="margin-top:8px">
<span id="days-summary" class="muted"></span>
</div>
@@ -39,8 +39,8 @@
<div class="panel">
<h2>2. Plan</h2>
<div class="row">
<label>From <input type="date" id="from"></label>
<label>To <input type="date" id="to"></label>
<label>From <input data-kind="date" id="from"></label>
<label>To <input data-kind="date" id="to"></label>
<button id="build" class="primary">Compare with Kimai &amp; plan</button>
<span id="plan-status" class="muted"></span>
</div>
@@ -66,7 +66,7 @@
<div class="row">
<label>Name <input id="r-name" placeholder="Weekly meeting"></label>
<label>Hours <input id="r-hours" size="6" placeholder="1 / 1:30"></label>
<label>Start time <input id="r-start" type="time"></label>
<label>Start time <input data-kind="time" id="r-start"></label>
<label class="inline"><input type="checkbox" id="r-enabled" checked> enabled</label>
</div>
<div class="row" style="margin-top:8px">
@@ -79,9 +79,9 @@
</div>
<div class="row" style="margin-top:8px">
<label>Every N weeks <input id="r-every" type="number" min="1" value="1" style="width:70px"></label>
<label>Week anchor <input id="r-anchor" type="date"></label>
<label>Valid from <input id="r-from" type="date"></label>
<label>Valid to <input id="r-to" type="date"></label>
<label>Week anchor <input data-kind="date" id="r-anchor"></label>
<label>Valid from <input data-kind="date" id="r-from"></label>
<label>Valid to <input data-kind="date" id="r-to"></label>
</div>
<div class="row" style="margin-top:12px">
<button id="r-save" class="primary">Save rule</button>
@@ -126,7 +126,7 @@
</div>
<div class="row" style="margin-top:8px">
<label>Tags <input id="s-tags"></label>
<label>Day start (when Abacus has only totals) <input id="s-daystart" type="time"></label>
<label>Day start (when Abacus has only totals) <input data-kind="time" id="s-daystart"></label>
<label class="inline"><input type="checkbox" id="s-skip"> skip a rule if an entry with the same project/activity already exists that day</label>
</div>
</div>
+20 -17
View File
@@ -70,8 +70,8 @@ function updateDaysSummary() {
? `${days.length} day(s), ${U.fmtDur(total)} total` + (errors.length ? ` — ${errors.join('; ')}` : '')
: errors.join('; ');
$('days-summary').className = errors.length ? 'warn' : 'muted';
if (days.length && !$('from').value) $('from').value = days[0].date;
if (days.length && !$('to').value) $('to').value = days[days.length - 1].date;
if (days.length && !$('from').value) U.setField($('from'), days[0].date);
if (days.length && !$('to').value) U.setField($('to'), days[days.length - 1].date);
}
$('days').addEventListener('input', () => {
@@ -95,7 +95,7 @@ $('parse-raw').addEventListener('click', () => {
async function buildPlan() {
$('plan-status').textContent = 'Loading Kimai entries…';
$('plan-status').className = 'muted';
const from = $('from').value, to = $('to').value;
const from = U.getField($('from')), to = U.getField($('to'));
const days = currentDays().days.filter((d) => (!from || d.date >= from) && (!to || d.date <= to));
if (!days.length) throw new Error('No Abacus days in range');
@@ -134,7 +134,7 @@ function renderPlan() {
type: 'checkbox', checked: p.include, disabled: !p.entries.length,
onchange: (ev) => { p.include = ev.target.checked; },
})),
h('td', { class: 'num' }, `${U.weekdayName(p.date)} ${p.date}`),
h('td', { class: 'num' }, `${U.weekdayName(p.date)} ${U.fmtDate(p.date)}`),
h('td', { class: 'num' }, U.fmtDur(p.target)),
h('td', { class: 'num' }, U.fmtDur(p.existingMin)),
h('td', { class: 'num' }, p.plannedMin ? U.fmtDur(p.plannedMin) : '—'),
@@ -205,13 +205,13 @@ function resetRuleForm(rule = null) {
$('rule-form-title').textContent = rule ? `Edit rule “${rule.name}”` : 'New rule';
$('r-name').value = rule?.name || '';
$('r-hours').value = rule && +rule.minutes ? U.fmtDur(rule.minutes) : '';
$('r-start').value = rule?.startTime || '';
U.setField($('r-start'), rule?.startTime);
$('r-enabled').checked = rule ? rule.enabled : true;
$('r-desc').value = rule?.description || '';
$('r-every').value = rule?.everyNWeeks || 1;
$('r-anchor').value = rule?.anchorDate || '';
$('r-from').value = rule?.validFrom || '';
$('r-to').value = rule?.validTo || '';
U.setField($('r-anchor'), rule?.anchorDate);
U.setField($('r-from'), rule?.validFrom);
U.setField($('r-to'), rule?.validTo);
const wd = rule?.weekdays || [1];
$('r-weekdays').querySelectorAll('input').forEach((i) => { i.checked = wd.includes(+i.value); });
fillProjectSelect($('r-project'), rule?.project);
@@ -227,6 +227,8 @@ $('r-save').addEventListener('click', async () => {
const weekdays = [...$('r-weekdays').querySelectorAll('input:checked')].map((i) => +i.value);
const err = (m) => { $('r-status').textContent = m; $('r-status').className = 'err'; };
if (hoursRaw && !minutes) return err('Invalid hours');
const bad = ['r-start', 'r-anchor', 'r-from', 'r-to'].find((id) => $(id).value.trim() && !U.getField($(id)));
if (bad) return err(`Invalid ${$(bad).dataset.kind === 'date' ? 'date (dd/mm/yyyy)' : 'time (hh:mm)'}`);
if (!weekdays.length) return err('Pick at least one weekday');
if (!$('r-project').value || !$('r-activity').value) return err('Pick project and activity');
@@ -235,14 +237,14 @@ $('r-save').addEventListener('click', async () => {
name: $('r-name').value.trim() || 'Rule',
enabled: $('r-enabled').checked,
weekdays, minutes,
startTime: $('r-start').value,
startTime: U.getField($('r-start')),
project: +$('r-project').value,
activity: +$('r-activity').value,
description: $('r-desc').value.trim(),
everyNWeeks: Math.max(1, +$('r-every').value || 1),
anchorDate: $('r-anchor').value,
validFrom: $('r-from').value,
validTo: $('r-to').value,
anchorDate: U.getField($('r-anchor')),
validFrom: U.getField($('r-from')),
validTo: U.getField($('r-to')),
};
const i = state.rules.findIndex((r) => r.id === rule.id);
if (i >= 0) state.rules[i] = rule; else state.rules.push(rule);
@@ -256,7 +258,7 @@ function describeWhen(r) {
let s = r.weekdays.map((d) => U.WEEKDAYS[d - 1]).join(', ');
if (+r.everyNWeeks > 1) s += ` · every ${r.everyNWeeks} weeks`;
if (r.startTime) s += ` @ ${r.startTime}`;
if (r.validFrom || r.validTo) s += ` · ${r.validFrom || '…'} → ${r.validTo || '…'}`;
if (r.validFrom || r.validTo) s += ` · ${U.fmtDate(r.validFrom) || '…'} → ${U.fmtDate(r.validTo) || '…'}`;
return s;
}
@@ -296,7 +298,7 @@ function loadSettingsForm() {
$('s-token').value = s.kimaiToken;
$('s-desc').value = s.defaultDescription;
$('s-tags').value = s.tags;
$('s-daystart').value = s.dayStart;
U.setField($('s-daystart'), s.dayStart);
$('s-skip').checked = s.skipIfRulePresent;
$('p-selector').value = s.parse.rowSelector;
$('p-mode').value = s.parse.timeMode;
@@ -319,7 +321,7 @@ function readSettingsForm() {
defaultActivity: +$('s-activity').value || null,
defaultDescription: $('s-desc').value.trim(),
tags: $('s-tags').value.trim(),
dayStart: $('s-daystart').value || '08:00',
dayStart: U.getField($('s-daystart')) || '08:00',
skipIfRulePresent: $('s-skip').checked,
parse: {
rowSelector: $('p-selector').value.trim(),
@@ -410,13 +412,14 @@ $('s-import').addEventListener('change', async (ev) => {
/* ---------- init ---------- */
(async () => {
U.setupFields();
const s = await Store.get();
state.settings = s.settings;
state.rules = s.rules;
state.catalog = s.catalog;
$('days').value = s.daysText;
if (s.extractInfo) {
$('extract-info').textContent = `Last extraction: ${s.extractInfo.count} day(s) from ${s.extractInfo.url} at ${new Date(s.extractInfo.at).toLocaleString()}`;
$('extract-info').textContent = `Last extraction: ${s.extractInfo.count} day(s) from ${s.extractInfo.url} at ${U.fmtDateTime(new Date(s.extractInfo.at))}`;
$('raw-last').textContent = (s.extractInfo.raw || []).join('\n');
}
loadSettingsForm();
@@ -436,7 +439,7 @@ browser.storage.onChanged.addListener((changes) => {
}
if (changes.extractInfo?.newValue) {
const i = changes.extractInfo.newValue;
$('extract-info').textContent = `Last extraction: ${i.count} day(s) from ${i.url} at ${new Date(i.at).toLocaleString()}`;
$('extract-info').textContent = `Last extraction: ${i.count} day(s) from ${i.url} at ${U.fmtDateTime(new Date(i.at))}`;
$('raw-last').textContent = (i.raw || []).join('\n');
}
});