Einstellungen: Vorauswahl für KI-Generierung (Dokumente + Anlagen)
Zwei neue, pro Benutzer gespeicherte Konfigurationswerte: GEN_DOKUMENTE_DEFAULT (Standard: Anschreiben + Lebenslauf) und GEN_ANLAGEN_DEFAULT (Standard: keine). Sie belegen die Häkchen auf der Bewerbungsseite vor; eine bereits getroffene Dokumentenauswahl der Bewerbung gewinnt weiterhin. Die REST-API nutzt die Vorauswahl, wenn dokumente/anlagen im Body fehlen. Die Einstellungsseite kennt dafür jetzt Checkbox-Gruppen, deren Optionen auch erst zur Renderzeit feststehen dürfen (die eigenen Anlagen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+10
-6
@@ -12,7 +12,7 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const blacklist = require('./blacklist');
|
const blacklist = require('./blacklist');
|
||||||
const { LABEL_OPTIONS, parseLabels, serializeLabels } = require('./labels');
|
const { LABEL_OPTIONS, parseLabels, serializeLabels } = require('./labels');
|
||||||
const { normalizeDokumente } = require('./documents');
|
const { normalizeDokumente, standardDokumente } = require('./documents');
|
||||||
const { userContext, currentUserId } = require('./context');
|
const { userContext, currentUserId } = require('./context');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
@@ -452,14 +452,18 @@ function createExternalApi(deps) {
|
|||||||
[id, uid()]
|
[id, uid()]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Optional: IDs of static attachments (basis_anhaenge) to enclose; none
|
// Optional: IDs of static attachments (basis_anhaenge) to enclose. Omitted
|
||||||
// by default. Also reflected in the cover letter's "Anlagen" list.
|
// means the user's preselection from the settings (GEN_ANLAGEN_DEFAULT,
|
||||||
|
// default: none); an empty array means explicitly none. Also reflected in
|
||||||
|
// the cover letter's "Anlagen" list.
|
||||||
const anlagenIds = Array.isArray((req.body || {}).anlagen)
|
const anlagenIds = Array.isArray((req.body || {}).anlagen)
|
||||||
? req.body.anlagen.map((v) => parseInt(v, 10)).filter((n) => !Number.isNaN(n))
|
? req.body.anlagen.map((v) => parseInt(v, 10)).filter((n) => !Number.isNaN(n))
|
||||||
: [];
|
: config.anlagenDefaultIds();
|
||||||
// Optional: which documents to produce (["anschreiben"], ["lebenslauf"] or
|
// Optional: which documents to produce (["anschreiben"], ["lebenslauf"] or
|
||||||
// both). Omitted / empty means both.
|
// both). Omitted / empty falls back to the user's preselection from the
|
||||||
const dokumente = normalizeDokumente((req.body || {}).dokumente);
|
// settings (GEN_DOKUMENTE_DEFAULT, default: both).
|
||||||
|
const gewuenscht = (req.body || {}).dokumente;
|
||||||
|
const dokumente = gewuenscht == null ? standardDokumente() : normalizeDokumente(gewuenscht);
|
||||||
await dbRun('UPDATE bewerbungen SET generierung_dokumente = ? WHERE id = ? AND user_id = ?', [dokumente.join(','), id, uid()]);
|
await dbRun('UPDATE bewerbungen SET generierung_dokumente = ? WHERE id = ? AND user_id = ?', [dokumente.join(','), id, uid()]);
|
||||||
runGeneration(id, { anlagenIds, dokumente });
|
runGeneration(id, { anlagenIds, dokumente });
|
||||||
res.status(202).json({ success: true, dokumente });
|
res.status(202).json({ success: true, dokumente });
|
||||||
|
|||||||
+43
-1
@@ -26,6 +26,12 @@ const DEFAULTS = {
|
|||||||
OLLAMA_MODEL: 'glm-5.2:cloud',
|
OLLAMA_MODEL: 'glm-5.2:cloud',
|
||||||
OLLAMA_HOST: 'https://ollama.com',
|
OLLAMA_HOST: 'https://ollama.com',
|
||||||
OLLAMA_TIMEOUT_MS: '300000',
|
OLLAMA_TIMEOUT_MS: '300000',
|
||||||
|
// Which documents are preselected when a generation is started (comma separated,
|
||||||
|
// see DOKUMENT_TYPEN in lib/documents.js). Empty means both.
|
||||||
|
GEN_DOKUMENTE_DEFAULT: 'anschreiben,lebenslauf',
|
||||||
|
// Which static attachments (basis_anhaenge IDs, comma separated) are preselected.
|
||||||
|
// Empty = none, which is the default.
|
||||||
|
GEN_ANLAGEN_DEFAULT: '',
|
||||||
// E-Mail (SMTP submission + IMAP receive).
|
// E-Mail (SMTP submission + IMAP receive).
|
||||||
MAIL_HOST: '',
|
MAIL_HOST: '',
|
||||||
MAIL_SMTP_PORT: '587',
|
MAIL_SMTP_PORT: '587',
|
||||||
@@ -57,6 +63,32 @@ const FIELDS = [
|
|||||||
{ key: 'OLLAMA_TIMEOUT_MS', label: 'Timeout (ms)', help: 'Standard: 300000 (5 Min.)' },
|
{ key: 'OLLAMA_TIMEOUT_MS', label: 'Timeout (ms)', help: 'Standard: 300000 (5 Min.)' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
titel: 'Bewerbungsunterlagen (Vorauswahl)',
|
||||||
|
beschreibung: 'Was ist auf der Bewerbungsseite vorab angehakt, wenn die KI-Generierung gestartet wird? Pro Bewerbung lässt sich die Auswahl weiterhin ändern; die zuletzt gewählten Dokumente bleiben an der jeweiligen Bewerbung erhalten.',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: 'GEN_DOKUMENTE_DEFAULT',
|
||||||
|
label: 'Standardmäßig generieren',
|
||||||
|
type: 'checkboxes',
|
||||||
|
options: [
|
||||||
|
{ value: 'anschreiben', label: 'Anschreiben' },
|
||||||
|
{ value: 'lebenslauf', label: 'Lebenslauf' },
|
||||||
|
],
|
||||||
|
help: 'Standard: beides. Die E-Mail-Begleitnachricht wird immer erzeugt. Ohne Häkchen gilt wieder beides.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'GEN_ANLAGEN_DEFAULT',
|
||||||
|
label: 'Standardmäßig beigelegte Anlagen',
|
||||||
|
type: 'checkboxes',
|
||||||
|
// Auswahlmöglichkeiten sind die eigenen Anlagen aus „Vorlagen“ und stehen
|
||||||
|
// erst zur Renderzeit fest — die Route reicht sie unter diesem Namen hinein.
|
||||||
|
optionsFrom: 'anlagen',
|
||||||
|
leerHinweis: 'Noch keine Anlagen hinterlegt – unter „Vorlagen“ kannst du Zeugnisse & Co. hochladen.',
|
||||||
|
help: 'Standard: keine. Angehakte Anlagen werden bei jeder Generierung vorausgewählt und im Anschreiben unter „Anlagen“ aufgeführt.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
titel: 'E-Mail (SMTP-Versand + IMAP-Empfang)',
|
titel: 'E-Mail (SMTP-Versand + IMAP-Empfang)',
|
||||||
beschreibung: 'Versand läuft über den eigenen Mailserver (DKIM/SPF/DMARC-Alignment). Ohne Host/Benutzer/Passwort ist der E-Mail-Teil deaktiviert. Pro Benutzer eigenes Postfach.',
|
beschreibung: 'Versand läuft über den eigenen Mailserver (DKIM/SPF/DMARC-Alignment). Ohne Host/Benutzer/Passwort ist der E-Mail-Teil deaktiviert. Pro Benutzer eigenes Postfach.',
|
||||||
@@ -159,6 +191,16 @@ function getAll() {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The current user's preselected static attachments (GEN_ANLAGEN_DEFAULT) as
|
||||||
|
// basis_anhaenge IDs. Deleted attachments may linger in the list; callers match
|
||||||
|
// them against the existing rows, so stale IDs simply never hit.
|
||||||
|
function anlagenDefaultIds() {
|
||||||
|
return String(get('GEN_ANLAGEN_DEFAULT') || '')
|
||||||
|
.split(',')
|
||||||
|
.map((v) => parseInt(v, 10))
|
||||||
|
.filter((n) => !Number.isNaN(n));
|
||||||
|
}
|
||||||
|
|
||||||
// Ollama bundle (shared by lib/documents.js + lib/chat.js), for the current user.
|
// Ollama bundle (shared by lib/documents.js + lib/chat.js), for the current user.
|
||||||
function ollama() {
|
function ollama() {
|
||||||
return {
|
return {
|
||||||
@@ -213,5 +255,5 @@ async function setForUser(userId, key, value) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DEFAULTS, FIELDS, PREFIX,
|
DEFAULTS, FIELDS, PREFIX,
|
||||||
get, getAll, ollama, init, saveAll, setForUser, ensureLoaded, invalidate,
|
get, getAll, ollama, anlagenDefaultIds, init, saveAll, setForUser, ensureLoaded, invalidate,
|
||||||
};
|
};
|
||||||
+9
-1
@@ -153,13 +153,20 @@ const OUTPUT_SCHEMA = {
|
|||||||
const DOKUMENT_TYPEN = ['anschreiben', 'lebenslauf'];
|
const DOKUMENT_TYPEN = ['anschreiben', 'lebenslauf'];
|
||||||
|
|
||||||
// Accept anything (string, array, undefined) and return a clean, ordered list.
|
// Accept anything (string, array, undefined) and return a clean, ordered list.
|
||||||
// Empty / unknown input means "both" — the default everywhere.
|
// Empty / unknown input means "both" — the fallback everywhere.
|
||||||
function normalizeDokumente(v) {
|
function normalizeDokumente(v) {
|
||||||
const gewaehlt = [].concat(v == null ? [] : v).map((x) => String(x).trim().toLowerCase());
|
const gewaehlt = [].concat(v == null ? [] : v).map((x) => String(x).trim().toLowerCase());
|
||||||
const gefiltert = DOKUMENT_TYPEN.filter((d) => gewaehlt.includes(d));
|
const gefiltert = DOKUMENT_TYPEN.filter((d) => gewaehlt.includes(d));
|
||||||
return gefiltert.length ? gefiltert : DOKUMENT_TYPEN.slice();
|
return gefiltert.length ? gefiltert : DOKUMENT_TYPEN.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The current user's preselection (Einstellungen → GEN_DOKUMENTE_DEFAULT): which
|
||||||
|
// documents are ticked when a generation is started without an explicit choice.
|
||||||
|
// An empty / unknown setting falls back to both.
|
||||||
|
function standardDokumente() {
|
||||||
|
return normalizeDokumente(String(config.get('GEN_DOKUMENTE_DEFAULT') || '').split(','));
|
||||||
|
}
|
||||||
|
|
||||||
// Ask the model only for the documents we actually want. Dropping a section from
|
// Ask the model only for the documents we actually want. Dropping a section from
|
||||||
// the schema means the model never writes it — that saves a chunk of generation
|
// the schema means the model never writes it — that saves a chunk of generation
|
||||||
// time and tokens when only one document is needed.
|
// time and tokens when only one document is needed.
|
||||||
@@ -3403,4 +3410,5 @@ module.exports = {
|
|||||||
renderDesignVorschau,
|
renderDesignVorschau,
|
||||||
DOKUMENT_TYPEN,
|
DOKUMENT_TYPEN,
|
||||||
normalizeDokumente,
|
normalizeDokumente,
|
||||||
|
standardDokumente,
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-4
@@ -373,15 +373,16 @@ function buildOpenApiSpec(baseUrl = '') {
|
|||||||
items: { type: 'integer' },
|
items: { type: 'integer' },
|
||||||
description:
|
description:
|
||||||
'IDs der zusätzlich beizulegenden statischen Anlagen (basis_anhaenge). ' +
|
'IDs der zusätzlich beizulegenden statischen Anlagen (basis_anhaenge). ' +
|
||||||
'Standard: keine. Die Auswahl erscheint auch im Anschreiben unter "Anlagen".',
|
'Weglassen = Vorauswahl aus den Einstellungen (GEN_ANLAGEN_DEFAULT, Standard: keine), ' +
|
||||||
|
'leeres Array = ausdrücklich keine. Die Auswahl erscheint auch im Anschreiben unter "Anlagen".',
|
||||||
},
|
},
|
||||||
dokumente: {
|
dokumente: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: { type: 'string', enum: ['anschreiben', 'lebenslauf'] },
|
items: { type: 'string', enum: ['anschreiben', 'lebenslauf'] },
|
||||||
description:
|
description:
|
||||||
'Welche Unterlagen erzeugt werden sollen. Weglassen oder leer = beide (Standard). ' +
|
'Welche Unterlagen erzeugt werden sollen. Weglassen = Vorauswahl aus den Einstellungen ' +
|
||||||
'Wird der Lebenslauf nicht erzeugt, führt ihn das Anschreiben auch nicht unter ' +
|
'(GEN_DOKUMENTE_DEFAULT, Standard: beide). Wird der Lebenslauf nicht erzeugt, führt ihn ' +
|
||||||
'"Anlagen" auf, und der E-Mail-Begleittext kündigt ihn nicht an.',
|
'das Anschreiben auch nicht unter "Anlagen" auf, und der E-Mail-Begleittext kündigt ihn nicht an.',
|
||||||
example: ['anschreiben'],
|
example: ['anschreiben'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const multer = require('multer');
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
generateApplicationDocuments, generateEmailReply, generateFeinschliff, renderDesignVorschau,
|
generateApplicationDocuments, generateEmailReply, generateFeinschliff, renderDesignVorschau,
|
||||||
DOKUMENT_TYPEN, normalizeDokumente,
|
DOKUMENT_TYPEN, normalizeDokumente, standardDokumente,
|
||||||
} = require('./lib/documents');
|
} = require('./lib/documents');
|
||||||
const chat = require('./lib/chat');
|
const chat = require('./lib/chat');
|
||||||
const websuche = require('./lib/websuche');
|
const websuche = require('./lib/websuche');
|
||||||
@@ -2564,16 +2564,19 @@ initializeDatabase().then(async () => {
|
|||||||
anhaenge,
|
anhaenge,
|
||||||
interneAnhaenge,
|
interneAnhaenge,
|
||||||
emails,
|
emails,
|
||||||
// Last choice of documents (defaults to both), for pre-ticking the form.
|
// Last choice of documents for this application; if there is none yet, the
|
||||||
dokumentAuswahl: normalizeDokumente(
|
// user's preselection from the settings (default: both) pre-ticks the form.
|
||||||
application.generierung_dokumente ? String(application.generierung_dokumente).split(',') : null
|
dokumentAuswahl: application.generierung_dokumente
|
||||||
),
|
? normalizeDokumente(String(application.generierung_dokumente).split(','))
|
||||||
|
: standardDokumente(),
|
||||||
mailConfigured: mailer.isConfigured(),
|
mailConfigured: mailer.isConfigured(),
|
||||||
mailFrom: mailer.isConfigured() ? mailer.fromField() : '',
|
mailFrom: mailer.isConfigured() ? mailer.fromField() : '',
|
||||||
mailError: req.query.mailerror ? String(req.query.mailerror) : '',
|
mailError: req.query.mailerror ? String(req.query.mailerror) : '',
|
||||||
mailOk: req.query.mailok ? String(req.query.mailok) : '',
|
mailOk: req.query.mailok ? String(req.query.mailok) : '',
|
||||||
basisCount: basisCountRow ? basisCountRow.count : 0,
|
basisCount: basisCountRow ? basisCountRow.count : 0,
|
||||||
basisAnhaenge,
|
basisAnhaenge,
|
||||||
|
// Vorausgewählte Anlagen aus den Einstellungen (Standard: keine).
|
||||||
|
anlagenAuswahl: config.anlagenDefaultIds(),
|
||||||
termine,
|
termine,
|
||||||
caldavConfigured: caldav.isConfigured(),
|
caldavConfigured: caldav.isConfigured(),
|
||||||
caldavTz: caldav.TZ,
|
caldavTz: caldav.TZ,
|
||||||
@@ -3868,12 +3871,25 @@ initializeDatabase().then(async () => {
|
|||||||
// Alle Konfigurationswerte (Ollama, E-Mail, CalDAV, REST-API) liegen in der
|
// Alle Konfigurationswerte (Ollama, E-Mail, CalDAV, REST-API) liegen in der
|
||||||
// app_state-Tabelle und sind hier editierbar. Speichern wirkt sofort, ein
|
// app_state-Tabelle und sind hier editierbar. Speichern wirkt sofort, ein
|
||||||
// Neustart ist nicht nötig (die Libs lesen per config.get() zur Laufzeit).
|
// Neustart ist nicht nötig (die Libs lesen per config.get() zur Laufzeit).
|
||||||
app.get('/einstellungen', (req, res) => {
|
app.get('/einstellungen', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Optionen, die erst zur Laufzeit feststehen (Feld-Definition: optionsFrom).
|
||||||
|
const anlagen = await dbAll(
|
||||||
|
'SELECT id, name, dateiname FROM basis_anhaenge WHERE user_id = ? ORDER BY id ASC',
|
||||||
|
[uid()]
|
||||||
|
);
|
||||||
res.render('settings', {
|
res.render('settings', {
|
||||||
felder: config.FIELDS,
|
felder: config.FIELDS,
|
||||||
werte: config.getAll(),
|
werte: config.getAll(),
|
||||||
|
auswahllisten: {
|
||||||
|
anlagen: anlagen.map((a) => ({ value: String(a.id), label: a.name || a.dateiname })),
|
||||||
|
},
|
||||||
hideSettings: true,
|
hideSettings: true,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading settings:', error);
|
||||||
|
res.status(500).send('Serverfehler');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/einstellungen', async (req, res) => {
|
app.post('/einstellungen', async (req, res) => {
|
||||||
@@ -3881,7 +3897,18 @@ initializeDatabase().then(async () => {
|
|||||||
const werte = {};
|
const werte = {};
|
||||||
for (const sektion of config.FIELDS) {
|
for (const sektion of config.FIELDS) {
|
||||||
for (const f of sektion.items) {
|
for (const f of sektion.items) {
|
||||||
werte[f.key] = req.body[f.key] != null ? String(req.body[f.key]) : '';
|
const roh = req.body[f.key];
|
||||||
|
if (f.type === 'checkboxes') {
|
||||||
|
// Checkbox-Gruppe: Express liefert je nach Anzahl Häkchen einen String
|
||||||
|
// oder ein Array (inkl. des leeren Hidden-Werts) — als Komma-Liste ablegen.
|
||||||
|
werte[f.key] = []
|
||||||
|
.concat(roh == null ? [] : roh)
|
||||||
|
.map((v) => String(v).trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',');
|
||||||
|
} else {
|
||||||
|
werte[f.key] = roh != null ? String(roh) : '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -343,11 +343,12 @@
|
|||||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||||
placeholder="z. B. Musterfirma GmbH Musterstraße 12 10115 Berlin Ansprechpartner: Frau Müller"><%= application.llm_notizen || '' %></textarea>
|
placeholder="z. B. Musterfirma GmbH Musterstraße 12 10115 Berlin Ansprechpartner: Frau Müller"><%= application.llm_notizen || '' %></textarea>
|
||||||
|
|
||||||
<!-- Which documents to generate (both by default). -->
|
<!-- Which documents to generate (vorbelegt aus den Einstellungen). -->
|
||||||
<div class="mt-4" id="dokumentAuswahl">
|
<div class="mt-4" id="dokumentAuswahl">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Was soll generiert werden?</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Was soll generiert werden?</label>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||||
Standardmäßig werden Anschreiben und Lebenslauf erstellt. Wird der Lebenslauf abgewählt,
|
Vorbelegt ist deine Auswahl aus den <a href="/einstellungen" class="text-blue-600 dark:text-blue-400 hover:underline">Einstellungen</a>
|
||||||
|
(Standard: Anschreiben und Lebenslauf). Wird der Lebenslauf abgewählt,
|
||||||
führt ihn das Anschreiben auch nicht mehr unter „Anlagen“ auf.
|
führt ihn das Anschreiben auch nicht mehr unter „Anlagen“ auf.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-wrap gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
@@ -369,18 +370,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Optional extra attachments to enclose (none by default). -->
|
<!-- Optional extra attachments to enclose (vorbelegt aus den Einstellungen). -->
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Zusätzliche Anlagen</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Zusätzliche Anlagen</label>
|
||||||
<% if (basisAnhaenge && basisAnhaenge.length) { %>
|
<% if (basisAnhaenge && basisAnhaenge.length) { %>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||||
Wähle aus, welche Anlagen (z. B. Zeugnisse) mitgeschickt werden. Standardmäßig
|
Wähle aus, welche Anlagen (z. B. Zeugnisse) mitgeschickt werden. Vorbelegt ist deine Auswahl
|
||||||
ist nichts ausgewählt; die Auswahl wird auch im Anschreiben unter „Anlagen“ berücksichtigt.
|
aus den <a href="/einstellungen" class="text-blue-600 dark:text-blue-400 hover:underline">Einstellungen</a>
|
||||||
|
(Standard: keine); die Auswahl wird auch im Anschreiben unter „Anlagen“ berücksichtigt.
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<% basisAnhaenge.forEach(function(ba){ %>
|
<% basisAnhaenge.forEach(function(ba){ %>
|
||||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200">
|
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200">
|
||||||
<input type="checkbox" name="anlage" value="<%= ba.id %>"
|
<input type="checkbox" name="anlage" value="<%= ba.id %>"
|
||||||
|
<%= anlagenAuswahl.includes(ba.id) ? 'checked' : '' %>
|
||||||
class="rounded border-gray-300 dark:border-gray-600 text-blue-600">
|
class="rounded border-gray-300 dark:border-gray-600 text-blue-600">
|
||||||
<%= ba.name || ba.dateiname %>
|
<%= ba.name || ba.dateiname %>
|
||||||
<% if (ba.name && ba.dateiname && ba.name !== ba.dateiname) { %>
|
<% if (ba.name && ba.dateiname && ba.name !== ba.dateiname) { %>
|
||||||
|
|||||||
@@ -48,7 +48,38 @@
|
|||||||
var val = werte[f.key] != null ? werte[f.key] : '';
|
var val = werte[f.key] != null ? werte[f.key] : '';
|
||||||
var inputType = f.secret ? 'password' : 'text';
|
var inputType = f.secret ? 'password' : 'text';
|
||||||
var inputClass = 'w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm pr-10';
|
var inputClass = 'w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm pr-10';
|
||||||
|
var gewaehlt = String(val).split(',').map(function(s){ return s.trim(); });
|
||||||
|
// Feste Optionen aus der Feld-Definition oder – bei „optionsFrom“ –
|
||||||
|
// die zur Renderzeit geladenen Listen (z. B. die eigenen Anlagen).
|
||||||
|
var optionen = f.options || (f.optionsFrom ? (auswahllisten[f.optionsFrom] || []) : []);
|
||||||
%>
|
%>
|
||||||
|
<% if (f.type === 'checkboxes') { %>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<span class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
<%= f.label %>
|
||||||
|
<code class="ml-1 text-[11px] font-normal text-gray-400 dark:text-gray-500"><%= f.key %></code>
|
||||||
|
</span>
|
||||||
|
<!-- Leerer Wert, damit „nichts angehakt“ auch gespeichert wird. -->
|
||||||
|
<input type="hidden" name="<%= f.key %>" value="">
|
||||||
|
<% if (!optionen.length) { %>
|
||||||
|
<p class="text-sm text-gray-400 dark:text-gray-500"><%= f.leerHinweis || 'Keine Auswahl verfügbar.' %></p>
|
||||||
|
<% } else { %>
|
||||||
|
<div class="flex flex-wrap gap-x-6 gap-y-2">
|
||||||
|
<% optionen.forEach(function(o){ %>
|
||||||
|
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<input type="checkbox" name="<%= f.key %>" value="<%= o.value %>"
|
||||||
|
<%= gewaehlt.includes(String(o.value)) ? 'checked' : '' %>
|
||||||
|
class="rounded border-gray-300 dark:border-gray-600 text-blue-600">
|
||||||
|
<%= o.label %>
|
||||||
|
</label>
|
||||||
|
<% }); %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<% if (f.help) { %>
|
||||||
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-2"><%= f.help %></p>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<% return; } %>
|
||||||
<div class="<%= f.secret ? 'sm:col-span-2' : '' %>">
|
<div class="<%= f.secret ? 'sm:col-span-2' : '' %>">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="cfg-<%= f.key %>">
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="cfg-<%= f.key %>">
|
||||||
<%= f.label %>
|
<%= f.label %>
|
||||||
|
|||||||
Reference in New Issue
Block a user