fix(image-import): Zielmonat aus Live-Dropdown, Titel-Namen matchen, Importer-Instanziierung nach Sync-Boot
- Zielmonat/-jahr aus month-select/year-select statt veraltetem app.currentMonth (stand fix auf heute) - normalizeName entfernt Anreden/Titel (Herr/Frau/Dr.) -> matcht titellose Mitarbeiter, kein Doppelanlegen - Regression aus Sync-Boot behoben: app.js erzeugt imageImporter nach window.app (DOMContentLoaded-Listener lief zu frueh) - totes app.currentMonth/currentYear entfernt; SW-Cache v5->v6
This commit is contained in:
parent
7d50e5d938
commit
0001735207
4 changed files with 77 additions and 11 deletions
10
app.js
10
app.js
|
|
@ -13,9 +13,6 @@ class DienstplanApp {
|
|||
this.holidayProvider = new HolidayProvider();
|
||||
this.calculator = new BonusCalculator(this.holidayProvider);
|
||||
|
||||
this.currentMonth = new Date().getMonth() + 1;
|
||||
this.currentYear = new Date().getFullYear();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
|
@ -1237,4 +1234,11 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
app = new DienstplanApp();
|
||||
window.app = app;
|
||||
|
||||
// Bild-Import hier erzeugen (nicht in einem eigenen DOMContentLoaded-Listener):
|
||||
// window.app wird erst nach dem await oben gesetzt, ein paralleler Listener
|
||||
// liefe da noch ohne app und wuerde den Importer nie anlegen.
|
||||
if (window.ImageImporter && !window.imageImporter) {
|
||||
window.imageImporter = new window.ImageImporter(app);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -211,7 +211,14 @@ class ImageImporter {
|
|||
* @returns {string}
|
||||
*/
|
||||
normalizeName(name) {
|
||||
return String(name).toLowerCase().trim().replace(/\s+/g, ' ');
|
||||
return String(name)
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ')
|
||||
// Anreden/Titel am Anfang entfernen (auch gestapelt: "Herr Dr."),
|
||||
// damit "Herr Alsholi" auf einen ohne Anrede gespeicherten "Alsholi" matcht.
|
||||
.replace(/^((herr|frau|hr|fr|dr|prof)\.?\s+)+/i, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -288,6 +295,13 @@ class ImageImporter {
|
|||
key = input.trim();
|
||||
}
|
||||
|
||||
// Zielmonat/-jahr aus dem Live-Dropdown lesen (einzige Quelle der Wahrheit).
|
||||
// Ein gecachtes Feld driftet, sobald das Dropdown ohne Sync geaendert wird.
|
||||
const monthEl = document.getElementById('month-select');
|
||||
const yearEl = document.getElementById('year-select');
|
||||
const selMonth = monthEl ? parseInt(monthEl.value, 10) : NaN;
|
||||
const selYear = yearEl ? parseInt(yearEl.value, 10) : NaN;
|
||||
|
||||
this.session = {
|
||||
file: null,
|
||||
thumbnailUrl: null,
|
||||
|
|
@ -296,8 +310,9 @@ class ImageImporter {
|
|||
entries: [],
|
||||
unknowns: [],
|
||||
resolvedNames: new Map(),
|
||||
targetYear: this.app ? this.app.currentYear : new Date().getFullYear(),
|
||||
targetMonth: this.app ? this.app.currentMonth : (new Date().getMonth() + 1),
|
||||
targetYear: Number.isInteger(selYear) ? selYear : new Date().getFullYear(),
|
||||
targetMonth: (Number.isInteger(selMonth) && selMonth >= 1 && selMonth <= 12)
|
||||
? selMonth : (new Date().getMonth() + 1),
|
||||
detectedMonth: null,
|
||||
detectedYear: null,
|
||||
notes: []
|
||||
|
|
@ -853,6 +868,7 @@ Regeln:
|
|||
- Datum stets im ISO-Format YYYY-MM-DD.
|
||||
- Wenn das Bild einen Monatstitel zeigt (z.B. „November 2025"), gib \`month\` (1–12) und \`year\` (vierstellig) in der Antwort an. Sonst null.
|
||||
- Wenn ein Name unklar zu lesen ist, übernimm deinen besten Ratevorschlag und vermerke es in \`notes\`.
|
||||
- Gib Namen OHNE Anrede/Titel zurück (kein „Herr", „Frau", „Hr.", „Fr.", „Dr.", „Prof.") — nur den Nachnamen wie in der Tabelle.
|
||||
|
||||
Antworte STRIKT in diesem JSON-Schema und sonst nichts:
|
||||
{
|
||||
|
|
@ -864,13 +880,15 @@ Antworte STRIKT in diesem JSON-Schema und sonst nichts:
|
|||
"notes": ["string", ...]
|
||||
}`;
|
||||
|
||||
// Make available globally
|
||||
window.ImageImporter = ImageImporter;
|
||||
// Make available globally (Browser) + require-bar (Node-Tests)
|
||||
if (typeof window !== 'undefined') window.ImageImporter = ImageImporter;
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = ImageImporter;
|
||||
|
||||
// Auto-instantiate when DOM + app are ready
|
||||
// Fallback-Instantiierung: app.js erzeugt den Importer regulaer nach dem
|
||||
// Sync-Boot. Dieser Listener greift nur, falls app schon frueher bereitsteht.
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (window.app) {
|
||||
if (window.app && !window.imageImporter) {
|
||||
window.imageImporter = new ImageImporter(window.app);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
44
image-import.test.js
Normal file
44
image-import.test.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const ImageImporter = require('./image-import.js');
|
||||
|
||||
const imp = new ImageImporter(null);
|
||||
|
||||
test('normalizeName entfernt Anrede/Titel (auch gestapelt)', () => {
|
||||
assert.strictEqual(imp.normalizeName('Herr Alsholi'), 'alsholi');
|
||||
assert.strictEqual(imp.normalizeName('Frau Cabrera'), 'cabrera');
|
||||
assert.strictEqual(imp.normalizeName('Herr Dr. Azizi'), 'azizi');
|
||||
assert.strictEqual(imp.normalizeName('Hr. Schierholz'), 'schierholz');
|
||||
assert.strictEqual(imp.normalizeName('Prof. Müller'), 'müller');
|
||||
});
|
||||
|
||||
test('normalizeName laesst titellose Namen unveraendert', () => {
|
||||
assert.strictEqual(imp.normalizeName('Alsholi'), 'alsholi');
|
||||
assert.strictEqual(imp.normalizeName(' Elsharawy '), 'elsharawy');
|
||||
// "Frei" darf nicht als Titel "fr" fehlinterpretiert werden (kein Space danach)
|
||||
assert.strictEqual(imp.normalizeName('Frei'), 'frei');
|
||||
});
|
||||
|
||||
test('matchNames matcht Titel-Namen auf titellose Mitarbeiter (kein Doppelanlegen)', () => {
|
||||
const entries = [
|
||||
{ name: 'Herr Alsholi', date: '2026-06-10', share: 1.0 },
|
||||
{ name: 'Frau Cabrera', date: '2026-06-11', share: 1.0 },
|
||||
{ name: 'Herr Dr. Azizi', date: '2026-06-02', share: 1.0 },
|
||||
];
|
||||
const existing = ['Alsholi', 'Cabrera', 'Azizi'];
|
||||
const { matched, unknowns } = imp.matchNames(entries, existing);
|
||||
assert.strictEqual(unknowns.length, 0, 'keine Unbekannten mehr');
|
||||
assert.strictEqual(matched.length, 3);
|
||||
assert.strictEqual(matched[0].resolvedName, 'Alsholi');
|
||||
assert.strictEqual(matched[1].resolvedName, 'Cabrera');
|
||||
assert.strictEqual(matched[2].resolvedName, 'Azizi');
|
||||
});
|
||||
|
||||
test('matchNames meldet echten Unbekannten weiterhin als unknown', () => {
|
||||
const entries = [{ name: 'Herr Weiszflog', date: '2026-06-10', share: 1.0 }];
|
||||
const existing = ['Alsholi', 'Cabrera'];
|
||||
const { matched, unknowns } = imp.matchNames(entries, existing);
|
||||
assert.strictEqual(matched.length, 0);
|
||||
assert.strictEqual(unknowns.length, 1);
|
||||
assert.strictEqual(unknowns[0].candidate, 'Herr Weiszflog');
|
||||
});
|
||||
2
sw.js
2
sw.js
|
|
@ -1,4 +1,4 @@
|
|||
const CACHE_NAME = 'dienstplan-pro-v5';
|
||||
const CACHE_NAME = 'dienstplan-pro-v6';
|
||||
const ASSETS = [
|
||||
'./',
|
||||
'./index.html',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue