Enhance event management and newsletter import functionality: Introduce methods for applying drafts from a bulk queue and streamline event form handling. Update event selection logic in the newsletter import management component to support encoding and decoding of bulk selections, improving user experience and data handling.
This commit is contained in:
@@ -1323,7 +1323,13 @@ function normalizeText(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isHeading(line, heading) {
|
function isHeading(line, heading) {
|
||||||
return normalizeText(line) === normalizeText(heading);
|
const normalizedLine = normalizeText(line).replace(/[:\-–]\s*$/g, '');
|
||||||
|
const normalizedHeading = normalizeText(heading).replace(/[:\-–]\s*$/g, '');
|
||||||
|
return (
|
||||||
|
normalizedLine === normalizedHeading ||
|
||||||
|
normalizedLine.startsWith(`${normalizedHeading} `) ||
|
||||||
|
normalizedHeading.startsWith(`${normalizedLine} `)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSectionByHeading(lines, startHeading, endHeadings = []) {
|
function getSectionByHeading(lines, startHeading, endHeadings = []) {
|
||||||
@@ -1370,26 +1376,97 @@ function hasDateOrTime(line) {
|
|||||||
function buildDetailedItems(lines) {
|
function buildDetailedItems(lines) {
|
||||||
const result = [];
|
const result = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (let i = 0; i < lines.length; i++) {
|
const isSectionLabel = (line) => /^(gottesdienste|veranstaltungen)\s*:?\s*$/i.test(String(line || '').trim());
|
||||||
const current = lines[i];
|
const youthAnchorPattern = /\b(kinderkirche|kigosabo|kindergottesdienst|jungschar|heliand-?pfadfinder(?:innen)?|pfadfinder(?:innen)?|konfirmationsunterricht|konfirmanden|vorkonfirmandenkurs)\b/i;
|
||||||
if (!hasDateOrTime(current)) continue;
|
const splitForYouthAnchors = (line) => {
|
||||||
|
const compact = String(line || '').replace(/\s+/g, ' ').trim();
|
||||||
|
if (!compact) return [];
|
||||||
|
const withCuts = compact
|
||||||
|
.replace(/\s+(?=(kinderkirche|kigosabo|kindergottesdienst|jungschar|heliand-?pfadfinder(?:innen)?|pfadfinder(?:innen)?|konfirmationsunterricht|konfirmanden|vorkonfirmandenkurs)\b)/gi, ' || ')
|
||||||
|
.replace(/\s+(?=(montag:|dienstag:|mittwoch:|donnerstag:|freitag:|samstag:|sonntag:))/gi, ' || ');
|
||||||
|
return withCuts.split('||').map((s) => s.trim()).filter(Boolean);
|
||||||
|
};
|
||||||
|
const expandedLines = lines.flatMap(splitForYouthAnchors);
|
||||||
|
const isHardSectionBoundary = (line) => {
|
||||||
|
const n = normalizeText(line);
|
||||||
|
if (!n) return false;
|
||||||
|
return (
|
||||||
|
n.startsWith('besondere gottesdienste und veranstaltungen') ||
|
||||||
|
n.includes('nieder-erlenbach und harheim') ||
|
||||||
|
n.startsWith('wunderbarer norden') ||
|
||||||
|
n.startsWith('leben vor dem tod') ||
|
||||||
|
/^seite?\s*\d+$/i.test(String(line || '').trim()) ||
|
||||||
|
/^\d{1,3}$/.test(String(line || '').trim())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const isEntryStart = (line) => {
|
||||||
|
if (!line || looksLikeHeading(line) || isSectionLabel(line) || isHardSectionBoundary(line)) return false;
|
||||||
|
if (/^(montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag):/i.test(String(line).trim())) return true;
|
||||||
|
if (youthAnchorPattern.test(line)) return true;
|
||||||
|
const hasScheduleSignal =
|
||||||
|
hasDateOrTime(line) ||
|
||||||
|
/\b\d{1,2}[:.]\d{2}\s*(?:-\s*\d{1,2}[:.]\d{2}\s*)?uhr\b/i.test(line) ||
|
||||||
|
/\b(von|um)\s+\d{1,2}[:.]\d{2}\b/i.test(line) ||
|
||||||
|
/\b(montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag)s?\b/i.test(line) ||
|
||||||
|
/\btermine[:\s]/i.test(line);
|
||||||
|
if (youthAnchorPattern.test(line) && hasScheduleSignal) return true;
|
||||||
|
// Klassische Startzeilen in den PDFs:
|
||||||
|
// "So., 08.02. 11.00 Uhr ..." oder "Mi., 18.02. 19.00 - 20.30 Uhr ..."
|
||||||
|
if (/^(so|mo|di|mi|do|fr|sa)\.,?\s+\d{1,2}\.\d{1,2}\./i.test(line)) return true;
|
||||||
|
// Fallback: enthalt Datum + Uhrzeit in derselben Zeile.
|
||||||
|
return /\b\d{1,2}\.\d{1,2}\./.test(line) && /\b\d{1,2}[:.]\d{2}\s*(?:-\s*\d{1,2}[:.]\d{2}\s*)?uhr\b/i.test(line);
|
||||||
|
};
|
||||||
|
const hasYouthScheduleSignal = (line) =>
|
||||||
|
hasDateOrTime(line) ||
|
||||||
|
/\b\d{1,2}[:.]\d{2}\s*(?:-\s*\d{1,2}[:.]\d{2}\s*)?uhr\b/i.test(String(line || '')) ||
|
||||||
|
/\b(von|um)\s+\d{1,2}[:.]\d{2}\b/i.test(String(line || '')) ||
|
||||||
|
/\b(montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag)\b/i.test(String(line || '')) ||
|
||||||
|
/\btermine[:\s]/i.test(String(line || ''));
|
||||||
|
|
||||||
const prev = i > 0 ? lines[i - 1] : '';
|
for (let i = 0; i < expandedLines.length; i++) {
|
||||||
const next = i + 1 < lines.length ? lines[i + 1] : '';
|
const current = expandedLines[i];
|
||||||
|
if (!isEntryStart(current)) continue;
|
||||||
|
|
||||||
const parts = [];
|
const parts = [current];
|
||||||
if (prev && !hasDateOrTime(prev) && !looksLikeHeading(prev) && prev.length < 120) {
|
for (let j = i + 1; j < expandedLines.length; j++) {
|
||||||
parts.push(prev);
|
const next = expandedLines[j];
|
||||||
}
|
if (!next) break;
|
||||||
parts.push(current);
|
if (looksLikeHeading(next) || isSectionLabel(next) || isHardSectionBoundary(next)) break;
|
||||||
if (next && !hasDateOrTime(next) && !looksLikeHeading(next) && next.length < 120) {
|
if (isEntryStart(next)) {
|
||||||
|
const currentIsYouthAnchor = youthAnchorPattern.test(current);
|
||||||
|
const currentHasSchedule = hasYouthScheduleSignal(current);
|
||||||
|
const nextIsStandaloneScheduleLine = !youthAnchorPattern.test(next);
|
||||||
|
if (currentIsYouthAnchor && !currentHasSchedule && nextIsStandaloneScheduleLine) {
|
||||||
|
parts.push(next);
|
||||||
|
i = j;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isNoiseLine(next)) break;
|
||||||
parts.push(next);
|
parts.push(next);
|
||||||
|
i = j; // konsumierte Zeilen überspringen
|
||||||
}
|
}
|
||||||
const text = parts.join(' | ');
|
|
||||||
const key = text.toLowerCase();
|
const text = parts
|
||||||
if (!seen.has(key)) {
|
.join(' ')
|
||||||
|
.replace(/\s*-\s+(?=[A-Za-zÄÖÜäöüß])/g, '') // harte Zeilentrennung "Gemein- desaal" heilen
|
||||||
|
.replace(/\s+\|/g, ' |')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Falls eine neue Abschnittsüberschrift in derselben Zeile klebt,
|
||||||
|
// den Eintrag dort hart abschneiden.
|
||||||
|
const textCutAtInlineBoundary = text
|
||||||
|
.split(/\s+Besondere Gottesdienste und Veranstaltungen\b/i)[0]
|
||||||
|
.split(/\s+Wunderbarer Norden\b/i)[0]
|
||||||
|
.split(/\s+Leben vor dem Tod\b/i)[0]
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const key = textCutAtInlineBoundary.toLowerCase();
|
||||||
|
if (!seen.has(key) && textCutAtInlineBoundary.length > 0) {
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
result.push(text);
|
result.push(textCutAtInlineBoundary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -1451,12 +1528,41 @@ function extractRegularTermineDetails(lines) {
|
|||||||
/frauenfr[üu]hst[üu]ck/i,
|
/frauenfr[üu]hst[üu]ck/i,
|
||||||
/kinder- und jugendb[üu]cherei/i,
|
/kinder- und jugendb[üu]cherei/i,
|
||||||
/wunderkiste/i,
|
/wunderkiste/i,
|
||||||
|
/seniorenclub/i,
|
||||||
|
/seniorencaf[eé]/i,
|
||||||
];
|
];
|
||||||
|
const splitRegularLineIntoSegments = (line) => {
|
||||||
|
const compact = String(line || '').replace(/\s+/g, ' ').trim();
|
||||||
|
if (!compact) return [];
|
||||||
|
|
||||||
|
const withAnchorCuts = compact
|
||||||
|
.replace(/\s+(?=(kinderkirche|kigosabo|kindergottesdienst|jungschar|heliand-?pfadfinder(?:innen)?|pfadfinder(?:innen)?|konfirmationsunterricht|konfirmanden\s*[„"]|vorkonfirmandenkurs|m[aä]nnerpalaver|miriamtreff|frauenfr[üu]hst[üu]ck|wunderkiste)\b)/gi, ' || ')
|
||||||
|
.replace(/\s+(?=(montag:|dienstag:|mittwoch:|donnerstag:|freitag:|samstag:|sonntag:))/gi, ' || ');
|
||||||
|
|
||||||
|
return withAnchorCuts
|
||||||
|
.split('||')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandedLines = lines.flatMap(splitRegularLineIntoSegments);
|
||||||
const details = [];
|
const details = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (let i = 0; i < lines.length; i++) {
|
const isAnchorLine = (line) => anchors.some((r) => r.test(line));
|
||||||
const line = lines[i];
|
const isSubHeadingLike = (line) => {
|
||||||
if (!anchors.some((r) => r.test(line))) continue;
|
const t = normalizeText(line);
|
||||||
|
return (
|
||||||
|
/^kinder und jugendliche$/.test(t) ||
|
||||||
|
/^kinder und jugend$/.test(t) ||
|
||||||
|
/^maenner und frauen$/.test(t) ||
|
||||||
|
/^musik$/.test(t) ||
|
||||||
|
/^senioren$/.test(t)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < expandedLines.length; i++) {
|
||||||
|
const line = expandedLines[i];
|
||||||
|
if (!isAnchorLine(line)) continue;
|
||||||
if (isNoiseLine(line)) continue;
|
if (isNoiseLine(line)) continue;
|
||||||
if (/start des neuen konfirmanden-jahrganges/i.test(line)) continue;
|
if (/start des neuen konfirmanden-jahrganges/i.test(line)) continue;
|
||||||
if (/konfirmanden\s*\/\s*geburtstagsgr[üu][ßs]e/i.test(line)) continue;
|
if (/konfirmanden\s*\/\s*geburtstagsgr[üu][ßs]e/i.test(line)) continue;
|
||||||
@@ -1464,9 +1570,11 @@ function extractRegularTermineDetails(lines) {
|
|||||||
|
|
||||||
const parts = [line];
|
const parts = [line];
|
||||||
let hasScheduleSignal = hasDateOrTime(line) || /termine[:\s]/i.test(line);
|
let hasScheduleSignal = hasDateOrTime(line) || /termine[:\s]/i.test(line);
|
||||||
for (let j = i + 1; j < Math.min(lines.length, i + 3); j++) {
|
for (let j = i + 1; j < Math.min(expandedLines.length, i + 8); j++) {
|
||||||
const next = lines[j];
|
const next = expandedLines[j];
|
||||||
if (looksLikeHeading(next) || isNoiseLine(next)) break;
|
if (looksLikeHeading(next) || isSubHeadingLike(next) || isNoiseLine(next)) break;
|
||||||
|
// Sobald ein neuer Anker startet, endet der aktuelle Block.
|
||||||
|
if (isAnchorLine(next)) break;
|
||||||
if (hasDateOrTime(next) || /termine[:\s]/i.test(next) || /\bmontag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag\b/i.test(next)) {
|
if (hasDateOrTime(next) || /termine[:\s]/i.test(next) || /\bmontag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag\b/i.test(next)) {
|
||||||
parts.push(next);
|
parts.push(next);
|
||||||
hasScheduleSignal = true;
|
hasScheduleSignal = true;
|
||||||
@@ -1606,6 +1714,71 @@ function extractWorshipBlocks(lines) {
|
|||||||
return [...new Set(blocks.map((b) => b.trim()).filter(Boolean))];
|
return [...new Set(blocks.map((b) => b.trim()).filter(Boolean))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildEventSignature(line) {
|
||||||
|
const text = normalizeText(line);
|
||||||
|
const anchorPatterns = [
|
||||||
|
/kinderkirche/,
|
||||||
|
/kigosabo|kindergottesdienst/,
|
||||||
|
/jungschar/,
|
||||||
|
/konfirmationsunterricht/,
|
||||||
|
/konfirmanden/,
|
||||||
|
/vorkonfirmandenkurs/,
|
||||||
|
/pfadfinder/,
|
||||||
|
/miriamtreff/,
|
||||||
|
/maennerpalaver/,
|
||||||
|
/frauenfruehstueck/,
|
||||||
|
/seniorenclub/,
|
||||||
|
/seniorencafe|senioren-cafe/,
|
||||||
|
];
|
||||||
|
const anchor = (anchorPatterns.find((r) => r.test(text)) || /./).source;
|
||||||
|
|
||||||
|
const dates = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\b/g)]
|
||||||
|
.map((m) => `${String(m[1]).padStart(2, '0')}.${String(m[2]).padStart(2, '0')}`)
|
||||||
|
.sort()
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
const range = text.match(/\b(\d{1,2})[:.](\d{2})\s*-\s*(\d{1,2})[:.](\d{2})\s*uhr\b/i);
|
||||||
|
const single = text.match(/\b(\d{1,2})[:.](\d{2})\s*uhr\b/i);
|
||||||
|
const startTime = range
|
||||||
|
? `${String(range[1]).padStart(2, '0')}:${range[2]}`
|
||||||
|
: (single ? `${String(single[1]).padStart(2, '0')}:${single[2]}` : '');
|
||||||
|
const endTime = range ? `${String(range[3]).padStart(2, '0')}:${range[4]}` : '';
|
||||||
|
const openTermine = /termine\s*:\s*noch offen|noch offen/.test(text) ? 'open' : '';
|
||||||
|
|
||||||
|
const placePatterns = [
|
||||||
|
/kita sternenzelt/,
|
||||||
|
/gemeindehaus bonames/,
|
||||||
|
/gemeindehaus nieder-eschbach/,
|
||||||
|
/gemeindehaus nieder-erlenbach/,
|
||||||
|
/crutzenhof kalbach/,
|
||||||
|
/bonames/,
|
||||||
|
/kalbach/,
|
||||||
|
/nieder-eschbach/,
|
||||||
|
/nieder-erlenbach/,
|
||||||
|
/harheim/,
|
||||||
|
];
|
||||||
|
const place = (placePatterns.find((r) => r.test(text)) || /./).source;
|
||||||
|
|
||||||
|
return `${anchor}|${dates}|${startTime}|${endTime}|${openTermine}|${place}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dedupeBySignature(lines) {
|
||||||
|
const seen = new Set();
|
||||||
|
const result = [];
|
||||||
|
for (const line of lines || []) {
|
||||||
|
const key = buildEventSignature(line);
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
result.push(line);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCrossSectionDuplicates(primaryLines, secondaryLines) {
|
||||||
|
const primaryKeys = new Set((primaryLines || []).map((line) => buildEventSignature(line)));
|
||||||
|
return (secondaryLines || []).filter((line) => !primaryKeys.has(buildEventSignature(line)));
|
||||||
|
}
|
||||||
|
|
||||||
exports.importNewsletterPdf = async (req, res) => {
|
exports.importNewsletterPdf = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
@@ -1638,7 +1811,8 @@ exports.importNewsletterPdf = async (req, res) => {
|
|||||||
'Männer und Frauen',
|
'Männer und Frauen',
|
||||||
['Musik', 'Kinder und Jugendliche']
|
['Musik', 'Kinder und Jugendliche']
|
||||||
);
|
);
|
||||||
const regelmaessigLines = [...regelmaessigSection, ...maennerFrauenSection];
|
const seniorenKeywordLines = extractLinesByKeyword(lines, /seniorenclub|senioren-?caf[eé]/i);
|
||||||
|
const regelmaessigLines = [...regelmaessigSection, ...maennerFrauenSection, ...seniorenKeywordLines];
|
||||||
|
|
||||||
const besondereLines = getSectionByHeading(
|
const besondereLines = getSectionByHeading(
|
||||||
lines,
|
lines,
|
||||||
@@ -1662,29 +1836,39 @@ exports.importNewsletterPdf = async (req, res) => {
|
|||||||
const cleanedBesondere = filterNoise(besondereLines);
|
const cleanedBesondere = filterNoise(besondereLines);
|
||||||
const cleanedKinderJugend = filterNoise(kinderJugendLines);
|
const cleanedKinderJugend = filterNoise(kinderJugendLines);
|
||||||
|
|
||||||
const regelmaessigDetails = extractRegularTermineDetails(cleanedRegelmaessig);
|
const regelmaessigDetails = dedupeBySignature(extractRegularTermineDetails(cleanedRegelmaessig));
|
||||||
|
const seniorenDetails = dedupeBySignature(extractRegularTermineDetails(filterNoise(seniorenKeywordLines)));
|
||||||
|
const mergedRegelmaessigDetails = dedupeBySignature([...regelmaessigDetails, ...seniorenDetails]);
|
||||||
|
const kinderUndJugendDetails = dedupeBySignature(buildDetailedItems(cleanedKinderJugend));
|
||||||
|
const regelmaessigOhneSenioren = removeCrossSectionDuplicates(seniorenDetails, mergedRegelmaessigDetails);
|
||||||
|
const regelmaessigOhneJugend = removeCrossSectionDuplicates(kinderUndJugendDetails, regelmaessigOhneSenioren);
|
||||||
|
const miriamtreffDetails = dedupeBySignature(miriamtreffLines);
|
||||||
|
const regelmaessigBereinigt = removeCrossSectionDuplicates(miriamtreffDetails, regelmaessigOhneJugend);
|
||||||
|
|
||||||
const parsedWorshipBlocks = extractWorshipBlocks(cleanedGottesdienste);
|
const parsedWorshipBlocks = extractWorshipBlocks(cleanedGottesdienste);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
gottesdienste: parsedWorshipBlocks,
|
gottesdienste: parsedWorshipBlocks,
|
||||||
regelmaessigeTermine: regelmaessigDetails,
|
regelmaessigeTermine: regelmaessigBereinigt,
|
||||||
besondereGottesdienste: extractEventCandidates(cleanedBesondere),
|
besondereGottesdienste: extractEventCandidates(cleanedBesondere),
|
||||||
miriamtreff: miriamtreffLines,
|
miriamtreff: miriamtreffDetails,
|
||||||
kinderUndJugend: extractEventCandidates(cleanedKinderJugend),
|
kinderUndJugend: kinderUndJugendDetails,
|
||||||
frauenfruehstueck: frauenfruehstueckLines,
|
frauenfruehstueck: frauenfruehstueckLines,
|
||||||
|
senioren: seniorenDetails,
|
||||||
};
|
};
|
||||||
|
|
||||||
const details = {
|
const details = {
|
||||||
gottesdienste: parsedWorshipBlocks,
|
gottesdienste: parsedWorshipBlocks,
|
||||||
regelmaessigeTermine: regelmaessigDetails,
|
regelmaessigeTermine: regelmaessigBereinigt,
|
||||||
besondereGottesdienste: buildDetailedItems(cleanedBesondere),
|
besondereGottesdienste: buildDetailedItems(cleanedBesondere),
|
||||||
miriamtreff: miriamtreffLines,
|
miriamtreff: miriamtreffDetails,
|
||||||
kinderUndJugend: buildDetailedItems(cleanedKinderJugend),
|
kinderUndJugend: kinderUndJugendDetails,
|
||||||
frauenfruehstueck: frauenfruehstueckLines,
|
frauenfruehstueck: frauenfruehstueckLines,
|
||||||
|
senioren: seniorenDetails,
|
||||||
sectionInfo: {
|
sectionInfo: {
|
||||||
gottesdiensteLines: gottesdiensteLines.length,
|
gottesdiensteLines: gottesdiensteLines.length,
|
||||||
regelmaessigLines: regelmaessigLines.length,
|
regelmaessigLines: regelmaessigLines.length,
|
||||||
|
seniorenKeywordLines: seniorenKeywordLines.length,
|
||||||
besondereLines: besondereLines.length,
|
besondereLines: besondereLines.length,
|
||||||
kinderJugendLines: kinderJugendLines.length,
|
kinderJugendLines: kinderJugendLines.length,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,51 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTime,
|
formatTime,
|
||||||
|
applyDraftToForm(draft) {
|
||||||
|
const resolvedEventPlace =
|
||||||
|
this.eventPlaces.find((place) => place.id === draft?.event_place_id) ||
|
||||||
|
this.eventPlaces.find((place) =>
|
||||||
|
draft?.event_place_name &&
|
||||||
|
String(place?.name || '').toLowerCase().includes(String(draft.event_place_name).toLowerCase())
|
||||||
|
) ||
|
||||||
|
null;
|
||||||
|
this.selectedEvent = {
|
||||||
|
name: draft?.name || '',
|
||||||
|
description: '',
|
||||||
|
date: draft?.date || '',
|
||||||
|
time: draft?.time || '',
|
||||||
|
endTime: draft?.endTime || '',
|
||||||
|
eventTypeId: draft?.eventTypeId ?? null,
|
||||||
|
event_place_id: resolvedEventPlace?.id ?? draft?.event_place_id ?? null,
|
||||||
|
eventPlace: resolvedEventPlace,
|
||||||
|
__newsletterDateMode: draft?.dateMode || null,
|
||||||
|
__newsletterBulkDates: draft?.bulkDates || '',
|
||||||
|
};
|
||||||
|
this.showForm = true;
|
||||||
|
this.scrollToFormAndFocus();
|
||||||
|
},
|
||||||
|
getNextDraftFromBulkQueue() {
|
||||||
|
const rawQueue = localStorage.getItem('newsletter_import_event_bulk_queue');
|
||||||
|
if (!rawQueue) return null;
|
||||||
|
try {
|
||||||
|
const queue = JSON.parse(rawQueue);
|
||||||
|
if (!Array.isArray(queue) || queue.length === 0) {
|
||||||
|
localStorage.removeItem('newsletter_import_event_bulk_queue');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const [next, ...rest] = queue;
|
||||||
|
if (rest.length > 0) {
|
||||||
|
localStorage.setItem('newsletter_import_event_bulk_queue', JSON.stringify(rest));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('newsletter_import_event_bulk_queue');
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Lesen der Event-Bulk-Queue:', error);
|
||||||
|
localStorage.removeItem('newsletter_import_event_bulk_queue');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
try {
|
try {
|
||||||
const [eventResponse, institutionResponse, eventPlaceResponse, contactPersonResponse, eventTypeResponse] = await Promise.all([
|
const [eventResponse, institutionResponse, eventPlaceResponse, contactPersonResponse, eventTypeResponse] = await Promise.all([
|
||||||
@@ -156,31 +201,18 @@ export default {
|
|||||||
this.$router.push('/admin/newsletter-import');
|
this.$router.push('/admin/newsletter-import');
|
||||||
},
|
},
|
||||||
applyNewsletterDraft() {
|
applyNewsletterDraft() {
|
||||||
|
const nextFromQueue = this.getNextDraftFromBulkQueue();
|
||||||
|
if (nextFromQueue) {
|
||||||
|
this.applyDraftToForm(nextFromQueue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const raw = localStorage.getItem('newsletter_import_event_draft');
|
const raw = localStorage.getItem('newsletter_import_event_draft');
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
localStorage.removeItem('newsletter_import_event_draft');
|
localStorage.removeItem('newsletter_import_event_draft');
|
||||||
try {
|
try {
|
||||||
const draft = JSON.parse(raw);
|
const draft = JSON.parse(raw);
|
||||||
const resolvedEventPlace =
|
this.applyDraftToForm(draft);
|
||||||
this.eventPlaces.find((place) => place.id === draft?.event_place_id) ||
|
|
||||||
this.eventPlaces.find((place) =>
|
|
||||||
draft?.event_place_name &&
|
|
||||||
String(place?.name || '').toLowerCase().includes(String(draft.event_place_name).toLowerCase())
|
|
||||||
) ||
|
|
||||||
null;
|
|
||||||
this.selectedEvent = {
|
|
||||||
name: draft?.name || '',
|
|
||||||
description: '',
|
|
||||||
date: draft?.date || '',
|
|
||||||
time: draft?.time || '',
|
|
||||||
eventTypeId: draft?.eventTypeId ?? null,
|
|
||||||
event_place_id: resolvedEventPlace?.id ?? draft?.event_place_id ?? null,
|
|
||||||
eventPlace: resolvedEventPlace,
|
|
||||||
__newsletterDateMode: draft?.dateMode || null,
|
|
||||||
__newsletterBulkDates: draft?.bulkDates || '',
|
|
||||||
};
|
|
||||||
this.showForm = true;
|
|
||||||
this.scrollToFormAndFocus();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Übernehmen des Gemeindebrief-Entwurfs (Event):', error);
|
console.error('Fehler beim Übernehmen des Gemeindebrief-Entwurfs (Event):', error);
|
||||||
}
|
}
|
||||||
@@ -211,7 +243,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleEventSaved() {
|
handleEventSaved() {
|
||||||
this.showForm = false;
|
const nextFromQueue = this.getNextDraftFromBulkQueue();
|
||||||
|
if (nextFromQueue) {
|
||||||
|
this.applyDraftToForm(nextFromQueue);
|
||||||
|
} else {
|
||||||
|
this.showForm = false;
|
||||||
|
}
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
},
|
},
|
||||||
handleEventCancelled() {
|
handleEventCancelled() {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<li>Miriamtreff: {{ newsletterImportResult.parsed?.miriamtreff?.length || 0 }}</li>
|
<li>Miriamtreff: {{ newsletterImportResult.parsed?.miriamtreff?.length || 0 }}</li>
|
||||||
<li>Kinder und Jugend: {{ newsletterImportResult.parsed?.kinderUndJugend?.length || 0 }}</li>
|
<li>Kinder und Jugend: {{ newsletterImportResult.parsed?.kinderUndJugend?.length || 0 }}</li>
|
||||||
<li>Frauenfrühstück: {{ newsletterImportResult.parsed?.frauenfruehstueck?.length || 0 }}</li>
|
<li>Frauenfrühstück: {{ newsletterImportResult.parsed?.frauenfruehstueck?.length || 0 }}</li>
|
||||||
|
<li>Senioren: {{ newsletterImportResult.parsed?.senioren?.length || 0 }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div v-if="newsletterImportResult.details" class="details">
|
<div v-if="newsletterImportResult.details" class="details">
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
<h5>Regelmäßige Termine</h5>
|
<h5>Regelmäßige Termine</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(entry, idx) in newsletterImportResult.details.regelmaessigeTermine || []" :key="`r-${idx}`">
|
<li v-for="(entry, idx) in newsletterImportResult.details.regelmaessigeTermine || []" :key="`r-${idx}`">
|
||||||
<label><input type="checkbox" :value="entry" v-model="selectedEventEntries" /></label>
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Regelmäßige Termine')" v-model="selectedEventEntries" /></label>
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Regelmäßige Termine')">Als Event übernehmen</button>
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Regelmäßige Termine')">Als Event übernehmen</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
<h5>Besondere Gottesdienste</h5>
|
<h5>Besondere Gottesdienste</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(entry, idx) in newsletterImportResult.details.besondereGottesdienste || []" :key="`b-${idx}`">
|
<li v-for="(entry, idx) in newsletterImportResult.details.besondereGottesdienste || []" :key="`b-${idx}`">
|
||||||
<label><input type="checkbox" :value="entry" v-model="selectedEventEntries" /></label>
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Besondere Gottesdienste')" v-model="selectedEventEntries" /></label>
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Besondere Gottesdienste')">Als Event übernehmen</button>
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Besondere Gottesdienste')">Als Event übernehmen</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -82,7 +83,7 @@
|
|||||||
<h5>Miriamtreff</h5>
|
<h5>Miriamtreff</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(entry, idx) in newsletterImportResult.details.miriamtreff || []" :key="`m-${idx}`">
|
<li v-for="(entry, idx) in newsletterImportResult.details.miriamtreff || []" :key="`m-${idx}`">
|
||||||
<label><input type="checkbox" :value="entry" v-model="selectedEventEntries" /></label>
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Miriamtreff')" v-model="selectedEventEntries" /></label>
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Miriamtreff')">Als Event übernehmen</button>
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Miriamtreff')">Als Event übernehmen</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
<h5>Kinder und Jugend</h5>
|
<h5>Kinder und Jugend</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(entry, idx) in newsletterImportResult.details.kinderUndJugend || []" :key="`k-${idx}`">
|
<li v-for="(entry, idx) in newsletterImportResult.details.kinderUndJugend || []" :key="`k-${idx}`">
|
||||||
<label><input type="checkbox" :value="entry" v-model="selectedEventEntries" /></label>
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Kinder und Jugend')" v-model="selectedEventEntries" /></label>
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Kinder und Jugend')">Als Event übernehmen</button>
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Kinder und Jugend')">Als Event übernehmen</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -104,12 +105,23 @@
|
|||||||
<h5>Frauenfrühstück</h5>
|
<h5>Frauenfrühstück</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(entry, idx) in newsletterImportResult.details.frauenfruehstueck || []" :key="`f-${idx}`">
|
<li v-for="(entry, idx) in newsletterImportResult.details.frauenfruehstueck || []" :key="`f-${idx}`">
|
||||||
<label><input type="checkbox" :value="entry" v-model="selectedEventEntries" /></label>
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Frauenfrühstück')" v-model="selectedEventEntries" /></label>
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Frauenfrühstück')">Als Event übernehmen</button>
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Frauenfrühstück')">Als Event übernehmen</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-group">
|
||||||
|
<h5>Senioren</h5>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(entry, idx) in newsletterImportResult.details.senioren || []" :key="`s-${idx}`">
|
||||||
|
<label><input type="checkbox" :value="encodeBulkSelection(entry, 'Senioren')" v-model="selectedEventEntries" /></label>
|
||||||
|
<span>{{ entry }}</span>
|
||||||
|
<button type="button" class="transfer-button" @click="transferToEventForm(entry, 'Senioren')">Als Event übernehmen</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="newsletterImportResult.questions?.length" class="questions">
|
<div v-if="newsletterImportResult.questions?.length" class="questions">
|
||||||
@@ -146,40 +158,105 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
encodeBulkSelection(entry, category) {
|
||||||
|
return `${category}|||${entry}`;
|
||||||
|
},
|
||||||
|
decodeBulkSelection(token) {
|
||||||
|
const value = String(token || '');
|
||||||
|
const sep = '|||';
|
||||||
|
const idx = value.indexOf(sep);
|
||||||
|
if (idx < 0) {
|
||||||
|
return { category: 'Regelmäßige Termine', entry: value };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
category: value.slice(0, idx),
|
||||||
|
entry: value.slice(idx + sep.length),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buildEventDraft(entry, category) {
|
||||||
|
const parsed = this.parseDateAndTime(entry);
|
||||||
|
const mapping = this.inferEventMapping(entry, category);
|
||||||
|
const allDates = this.extractAllDates(entry);
|
||||||
|
const hasMultipleDates = allDates.length > 1;
|
||||||
|
const inferredTitle = this.extractTitle(entry);
|
||||||
|
const normalizedTitle = mapping.eventTypeId === 41 ? 'Kirche Kunterbunt' : inferredTitle;
|
||||||
|
return {
|
||||||
|
name: normalizedTitle,
|
||||||
|
description: '',
|
||||||
|
date: hasMultipleDates ? '' : parsed.isoDate,
|
||||||
|
time: parsed.time,
|
||||||
|
endTime: parsed.endTime,
|
||||||
|
dateMode: hasMultipleDates ? 'bulk' : 'date',
|
||||||
|
bulkDates: hasMultipleDates ? allDates.join(', ') : '',
|
||||||
|
category,
|
||||||
|
eventTypeId: mapping.eventTypeId,
|
||||||
|
event_place_id: mapping.event_place_id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isValidDayMonth(day, month) {
|
||||||
|
const dd = Number(day);
|
||||||
|
const mm = Number(month);
|
||||||
|
return Number.isInteger(dd) && Number.isInteger(mm) && dd >= 1 && dd <= 31 && mm >= 1 && mm <= 12;
|
||||||
|
},
|
||||||
parseDateAndTime(rawText) {
|
parseDateAndTime(rawText) {
|
||||||
const text = String(rawText || '');
|
const text = String(rawText || '');
|
||||||
const dateMatch = text.match(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/);
|
const longDateMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/g)];
|
||||||
const shortDateMatch = text.match(/\b(\d{1,2})\.(\d{1,2})\.\b/);
|
const shortDateMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(?!\d)(?!\s*uhr\b)/gi)];
|
||||||
// Akzeptiert "19:30 Uhr", "19.30 Uhr" und auch "19:30"/"19.30" ohne "Uhr".
|
// Zeit robust parsen:
|
||||||
const timeMatch = text.match(/\b(\d{1,2})[:.](\d{2})(?:\s*uhr)?\b/i);
|
// 1) Zeitspannen immer als Startzeit übernehmen: "17.00 - 20.00 Uhr" -> 17:00
|
||||||
|
// 2) Einzelzeiten: "19:30" / "19:30 Uhr" / "19.30 Uhr"
|
||||||
|
const rangeMatch =
|
||||||
|
text.match(/\b(?:von\s+)?(\d{1,2})[.:](\d{2})\s*-\s*\d{1,2}[.:]\d{2}\s*uhr\b/i);
|
||||||
|
const rangeEndMatch =
|
||||||
|
text.match(/\b(?:von\s+)?\d{1,2}[.:]\d{2}\s*-\s*(\d{1,2})[.:](\d{2})\s*uhr\b/i);
|
||||||
|
const colonTimeMatch = text.match(/\b(?:um\s+|von\s+)?(\d{1,2}):(\d{2})(?:\s*uhr)?\b/i);
|
||||||
|
// Punkt-Zeiten nur mit klarem Zeitkontext akzeptieren, damit 09.02. (Datum) nicht als Uhrzeit gilt.
|
||||||
|
const dotTimeWithContextMatch =
|
||||||
|
text.match(/\bum\s+(\d{1,2})\.(\d{2})\s*uhr\b/i) ||
|
||||||
|
text.match(/\bvon\s+(\d{1,2})\.(\d{2})\b/i) ||
|
||||||
|
text.match(/\b(\d{1,2})\.(\d{2})\s*uhr\b/i);
|
||||||
let isoDate = '';
|
let isoDate = '';
|
||||||
let time = '';
|
let time = '';
|
||||||
if (dateMatch) {
|
let endTime = '';
|
||||||
const dd = String(dateMatch[1]).padStart(2, '0');
|
|
||||||
const mm = String(dateMatch[2]).padStart(2, '0');
|
const firstLong = longDateMatches.find((m) => this.isValidDayMonth(m[1], m[2]));
|
||||||
const yyyy = dateMatch[3];
|
if (firstLong) {
|
||||||
|
const dd = String(firstLong[1]).padStart(2, '0');
|
||||||
|
const mm = String(firstLong[2]).padStart(2, '0');
|
||||||
|
const yyyy = firstLong[3];
|
||||||
isoDate = `${yyyy}-${mm}-${dd}`;
|
isoDate = `${yyyy}-${mm}-${dd}`;
|
||||||
} else if (shortDateMatch) {
|
} else {
|
||||||
|
const firstShort = shortDateMatches.find((m) => this.isValidDayMonth(m[1], m[2]));
|
||||||
|
if (firstShort) {
|
||||||
const nowYear = new Date().getFullYear();
|
const nowYear = new Date().getFullYear();
|
||||||
const dd = String(shortDateMatch[1]).padStart(2, '0');
|
const dd = String(firstShort[1]).padStart(2, '0');
|
||||||
const mm = String(shortDateMatch[2]).padStart(2, '0');
|
const mm = String(firstShort[2]).padStart(2, '0');
|
||||||
isoDate = `${nowYear}-${mm}-${dd}`;
|
isoDate = `${nowYear}-${mm}-${dd}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const timeMatch = rangeMatch || colonTimeMatch || dotTimeWithContextMatch;
|
||||||
if (timeMatch) {
|
if (timeMatch) {
|
||||||
const hh = String(timeMatch[1]).padStart(2, '0');
|
const hh = String(timeMatch[1]).padStart(2, '0');
|
||||||
const min = String(timeMatch[2]).padStart(2, '0');
|
const min = String(timeMatch[2]).padStart(2, '0');
|
||||||
time = `${hh}:${min}`;
|
time = `${hh}:${min}`;
|
||||||
}
|
}
|
||||||
return { isoDate, time };
|
if (rangeEndMatch) {
|
||||||
|
const hhEnd = String(rangeEndMatch[1]).padStart(2, '0');
|
||||||
|
const minEnd = String(rangeEndMatch[2]).padStart(2, '0');
|
||||||
|
endTime = `${hhEnd}:${minEnd}`;
|
||||||
|
}
|
||||||
|
return { isoDate, time, endTime };
|
||||||
},
|
},
|
||||||
extractAllDates(rawText) {
|
extractAllDates(rawText) {
|
||||||
const text = String(rawText || '');
|
const text = String(rawText || '');
|
||||||
const nowYear = new Date().getFullYear();
|
const nowYear = new Date().getFullYear();
|
||||||
|
|
||||||
const longMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/g)];
|
const longMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/g)];
|
||||||
const shortMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(?!\d)/g)];
|
const shortMatches = [...text.matchAll(/\b(\d{1,2})\.(\d{1,2})\.(?!\d)(?!\s*uhr\b)/gi)];
|
||||||
|
|
||||||
const normalized = longMatches.map((m) => {
|
const normalized = longMatches
|
||||||
|
.filter((m) => this.isValidDayMonth(m[1], m[2]))
|
||||||
|
.map((m) => {
|
||||||
const dd = String(m[1]).padStart(2, '0');
|
const dd = String(m[1]).padStart(2, '0');
|
||||||
const mm = String(m[2]).padStart(2, '0');
|
const mm = String(m[2]).padStart(2, '0');
|
||||||
const yyyy = m[3];
|
const yyyy = m[3];
|
||||||
@@ -187,6 +264,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
shortMatches.forEach((m) => {
|
shortMatches.forEach((m) => {
|
||||||
|
if (!this.isValidDayMonth(m[1], m[2])) return;
|
||||||
const dd = String(m[1]).padStart(2, '0');
|
const dd = String(m[1]).padStart(2, '0');
|
||||||
const mm = String(m[2]).padStart(2, '0');
|
const mm = String(m[2]).padStart(2, '0');
|
||||||
const fallback = `${dd}.${mm}.${nowYear}`;
|
const fallback = `${dd}.${mm}.${nowYear}`;
|
||||||
@@ -266,8 +344,17 @@ export default {
|
|||||||
const text = String(rawText || '').trim();
|
const text = String(rawText || '').trim();
|
||||||
const parts = text.split('|').map((p) => p.trim()).filter(Boolean);
|
const parts = text.split('|').map((p) => p.trim()).filter(Boolean);
|
||||||
if (parts.length === 0) return 'Import aus Gemeindebrief';
|
if (parts.length === 0) return 'Import aus Gemeindebrief';
|
||||||
|
// Bevorzugt den eigentlichen Titelteil aus dem Segment mit Datum/Uhrzeit.
|
||||||
|
const firstPart = parts[0] || '';
|
||||||
|
const fromFirstPart = firstPart
|
||||||
|
.replace(/^(?:so|mo|di|mi|do|fr|sa)\.,?\s*/i, '')
|
||||||
|
.replace(/^\d{1,2}\.\d{1,2}\.(?:\d{2,4})?\s*/i, '')
|
||||||
|
.replace(/^\d{1,2}[:.]\d{2}\s*(?:-\s*\d{1,2}[:.]\d{2}\s*)?uhr\s*/i, '')
|
||||||
|
.replace(/^\d{1,2}\.\d{1,2}\.\s*/i, '')
|
||||||
|
.trim();
|
||||||
const withoutDate = parts.find((p) => !/\d{1,2}\.\d{1,2}\./.test(p) && !/\d{1,2}[:.]\d{2}\s*uhr/i.test(p));
|
const withoutDate = parts.find((p) => !/\d{1,2}\.\d{1,2}\./.test(p) && !/\d{1,2}[:.]\d{2}\s*uhr/i.test(p));
|
||||||
const cleaned = (withoutDate || parts[0] || '')
|
const baseTitle = fromFirstPart || withoutDate || parts[0] || '';
|
||||||
|
const cleaned = baseTitle
|
||||||
.replace(/\[\[FLAG_NEIGHBOR_INVITATION\]\]/gi, '')
|
.replace(/\[\[FLAG_NEIGHBOR_INVITATION\]\]/gi, '')
|
||||||
.replace(/\[\[FLAG_SELF_INFORMATION\]\]/gi, '')
|
.replace(/\[\[FLAG_SELF_INFORMATION\]\]/gi, '')
|
||||||
.replace(/bitte informieren sie sich auch auf den internetseiten.*$/i, '')
|
.replace(/bitte informieren sie sich auch auf den internetseiten.*$/i, '')
|
||||||
@@ -277,44 +364,18 @@ export default {
|
|||||||
return cleaned || 'Import aus Gemeindebrief';
|
return cleaned || 'Import aus Gemeindebrief';
|
||||||
},
|
},
|
||||||
transferToEventForm(entry, category) {
|
transferToEventForm(entry, category) {
|
||||||
const parsed = this.parseDateAndTime(entry);
|
const draft = this.buildEventDraft(entry, category);
|
||||||
const mapping = this.inferEventMapping(entry, category);
|
|
||||||
const allDates = this.extractAllDates(entry);
|
|
||||||
const hasMultipleDates = allDates.length > 1;
|
|
||||||
const draft = {
|
|
||||||
name: this.extractTitle(entry),
|
|
||||||
description: '',
|
|
||||||
date: hasMultipleDates ? '' : parsed.isoDate,
|
|
||||||
time: parsed.time,
|
|
||||||
dateMode: hasMultipleDates ? 'bulk' : 'single',
|
|
||||||
bulkDates: hasMultipleDates ? allDates.join(', ') : '',
|
|
||||||
category,
|
|
||||||
eventTypeId: mapping.eventTypeId,
|
|
||||||
event_place_id: mapping.event_place_id,
|
|
||||||
};
|
|
||||||
localStorage.setItem('newsletter_import_event_draft', JSON.stringify(draft));
|
localStorage.setItem('newsletter_import_event_draft', JSON.stringify(draft));
|
||||||
this.$router.push('/admin/events');
|
this.$router.push('/admin/events');
|
||||||
},
|
},
|
||||||
transferSelectedToEventBulk() {
|
transferSelectedToEventBulk() {
|
||||||
if (this.selectedEventEntries.length === 0) return;
|
if (this.selectedEventEntries.length === 0) return;
|
||||||
const bulkDates = this.selectedEventEntries
|
const queue = this.selectedEventEntries
|
||||||
.map((entry) => {
|
.map((token) => this.decodeBulkSelection(token))
|
||||||
const parsed = this.parseDateAndTime(entry);
|
.filter((item) => item.entry)
|
||||||
if (!parsed.isoDate) return '';
|
.map((item) => this.buildEventDraft(item.entry, item.category));
|
||||||
const [yyyy, mm, dd] = parsed.isoDate.split('-');
|
if (queue.length === 0) return;
|
||||||
return `${dd}.${mm}.${yyyy}`;
|
localStorage.setItem('newsletter_import_event_bulk_queue', JSON.stringify(queue));
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(', ');
|
|
||||||
const first = this.selectedEventEntries[0] || '';
|
|
||||||
const draft = {
|
|
||||||
name: this.extractTitle(first),
|
|
||||||
description: '',
|
|
||||||
dateMode: 'bulk',
|
|
||||||
bulkDates,
|
|
||||||
...this.inferEventMapping(first, 'Regelmäßige Termine'),
|
|
||||||
};
|
|
||||||
localStorage.setItem('newsletter_import_event_draft', JSON.stringify(draft));
|
|
||||||
this.$router.push('/admin/events');
|
this.$router.push('/admin/events');
|
||||||
},
|
},
|
||||||
handleNewsletterPdfSelect(event) {
|
handleNewsletterPdfSelect(event) {
|
||||||
|
|||||||
Reference in New Issue
Block a user