feat(delivery): replace deliveryEditorSlice with automat and add PDF/text attachment handling
- replace Redux deliveryEditorSlice with deliveryEditorAutomat using automat - add Attachment and AttachmentList components in Kopfdaten - extract PDF text via pdftotext-wasm and render PDF thumbnails with pdfjs-dist - add TextEditorDialog to view and edit extracted text files (preformatted monospace, no wrap) - configure Vite server.fs.allow for pdftotext.wasm
This commit is contained in:
257
src/automats/deliveryEditorAutomat.js
Normal file
257
src/automats/deliveryEditorAutomat.js
Normal file
@@ -0,0 +1,257 @@
|
||||
import { Automat } from 'automat';
|
||||
|
||||
export function createEmptyLineitem(line = {}) {
|
||||
return {
|
||||
key: line.key ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: line.name ?? null,
|
||||
sku: line.sku ?? null,
|
||||
amount: line.amount ?? null,
|
||||
pu: line.pu ?? null,
|
||||
unit: line.unit ?? null,
|
||||
netprice: line.netprice ?? null,
|
||||
taxrate: line.taxrate ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export const initialDelivery = {
|
||||
id: null,
|
||||
deliveryId: null,
|
||||
date: null,
|
||||
deliveryEntity: null,
|
||||
partyId: null,
|
||||
externalNumber: null,
|
||||
external_number: null,
|
||||
number: null,
|
||||
type: null,
|
||||
lines: [],
|
||||
lineitems: [],
|
||||
notes: [],
|
||||
attachments: [],
|
||||
files: [],
|
||||
};
|
||||
|
||||
export const initialState = {
|
||||
deliveryEntities: [],
|
||||
...initialDelivery,
|
||||
};
|
||||
|
||||
export const deliveryEditorAutomat = new Automat(initialState, {
|
||||
setId(id) {
|
||||
deliveryEditorAutomat.setState({ id, deliveryId: id });
|
||||
},
|
||||
|
||||
setDeliveryEntities(deliveryEntities) {
|
||||
deliveryEditorAutomat.setState({ deliveryEntities: deliveryEntities ?? [] });
|
||||
},
|
||||
|
||||
setDelivery(delivery = {}) {
|
||||
const rawLines = delivery.lines ?? delivery.lineitems ?? [];
|
||||
const lines = Array.isArray(rawLines) ? rawLines : [];
|
||||
const partyId = delivery.partyId ?? delivery.deliveryEntity ?? null;
|
||||
const extNum = delivery.externalNumber ?? delivery.external_number ?? null;
|
||||
const id = delivery.id ?? delivery.deliveryId ?? null;
|
||||
const notes = Array.isArray(delivery.notes)
|
||||
? delivery.notes
|
||||
: delivery.notes ? [delivery.notes] : [];
|
||||
const rawAttachments = delivery.attachments ?? delivery.files ?? [];
|
||||
const attachments = Array.isArray(rawAttachments) ? rawAttachments : [];
|
||||
|
||||
deliveryEditorAutomat.setState({
|
||||
...initialDelivery,
|
||||
...delivery,
|
||||
id,
|
||||
deliveryId: id,
|
||||
partyId,
|
||||
deliveryEntity: partyId,
|
||||
externalNumber: extNum,
|
||||
external_number: extNum,
|
||||
lines,
|
||||
lineitems: lines,
|
||||
notes,
|
||||
attachments,
|
||||
files: attachments,
|
||||
});
|
||||
},
|
||||
|
||||
setDate(date) {
|
||||
deliveryEditorAutomat.setState({ date });
|
||||
},
|
||||
|
||||
setDeliveryEntity(entityId) {
|
||||
deliveryEditorAutomat.setState({ deliveryEntity: entityId, partyId: entityId });
|
||||
},
|
||||
|
||||
setPartyId(partyId) {
|
||||
deliveryEditorAutomat.setState({ deliveryEntity: partyId, partyId });
|
||||
},
|
||||
|
||||
setExternalNumber(externalNumber) {
|
||||
deliveryEditorAutomat.setState({
|
||||
externalNumber,
|
||||
external_number: externalNumber,
|
||||
});
|
||||
},
|
||||
|
||||
setNumber(number) {
|
||||
deliveryEditorAutomat.setState({ number });
|
||||
},
|
||||
|
||||
setType(type) {
|
||||
deliveryEditorAutomat.setState({ type });
|
||||
},
|
||||
|
||||
setLines(lines) {
|
||||
const cleanLines = lines ?? [];
|
||||
deliveryEditorAutomat.setState({ lines: cleanLines, lineitems: cleanLines });
|
||||
},
|
||||
|
||||
setLineitems(lines) {
|
||||
const cleanLines = lines ?? [];
|
||||
deliveryEditorAutomat.setState({ lines: cleanLines, lineitems: cleanLines });
|
||||
},
|
||||
|
||||
setNotes(notes) {
|
||||
const cleanNotes = Array.isArray(notes)
|
||||
? notes
|
||||
: notes ? [notes] : [];
|
||||
deliveryEditorAutomat.setState({ notes: cleanNotes });
|
||||
},
|
||||
|
||||
addLineitem(line = {}) {
|
||||
const item = createEmptyLineitem(line);
|
||||
const nextLines = [...(deliveryEditorAutomat.state.lines ?? []), item];
|
||||
deliveryEditorAutomat.setState({ lines: nextLines, lineitems: nextLines });
|
||||
},
|
||||
|
||||
addLine(line = {}) {
|
||||
deliveryEditorAutomat.actions.addLineitem(line);
|
||||
},
|
||||
|
||||
updateLineitem(payload) {
|
||||
const { index, key, ...fields } = payload || {};
|
||||
const currentLines = deliveryEditorAutomat.state.lines ?? [];
|
||||
let targetIndex = typeof index === 'number' && !Number.isNaN(index) ? index : -1;
|
||||
if (targetIndex === -1 && key != null) {
|
||||
targetIndex = currentLines.findIndex((l) => l.key === key);
|
||||
}
|
||||
if (targetIndex >= 0 && targetIndex < currentLines.length) {
|
||||
const nextLines = [...currentLines];
|
||||
nextLines[targetIndex] = {
|
||||
...nextLines[targetIndex],
|
||||
...fields,
|
||||
};
|
||||
deliveryEditorAutomat.setState({ lines: nextLines, lineitems: nextLines });
|
||||
}
|
||||
},
|
||||
|
||||
updateLine(payload) {
|
||||
deliveryEditorAutomat.actions.updateLineitem(payload);
|
||||
},
|
||||
|
||||
removeLineitem(payload) {
|
||||
const currentLines = deliveryEditorAutomat.state.lines ?? [];
|
||||
let index = typeof payload === 'number' && !Number.isNaN(payload) ? payload : -1;
|
||||
if (index === -1 && payload != null) {
|
||||
const key = typeof payload === 'string' ? payload : payload?.key;
|
||||
if (key != null) {
|
||||
index = currentLines.findIndex((l) => l.key === key);
|
||||
}
|
||||
}
|
||||
if (index >= 0 && index < currentLines.length) {
|
||||
const nextLines = currentLines.filter((_, i) => i !== index);
|
||||
deliveryEditorAutomat.setState({ lines: nextLines, lineitems: nextLines });
|
||||
}
|
||||
},
|
||||
|
||||
removeLine(payload) {
|
||||
deliveryEditorAutomat.actions.removeLineitem(payload);
|
||||
},
|
||||
|
||||
addAttachment(attachment) {
|
||||
const id = attachment.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const item = { ...attachment, id };
|
||||
const next = [...(deliveryEditorAutomat.state.attachments ?? []), item];
|
||||
deliveryEditorAutomat.setState({ attachments: next, files: next });
|
||||
},
|
||||
|
||||
setAttachments(attachments) {
|
||||
const clean = Array.isArray(attachments) ? attachments : [];
|
||||
deliveryEditorAutomat.setState({ attachments: clean, files: clean });
|
||||
},
|
||||
|
||||
updateAttachment(payload) {
|
||||
const { id, ...fields } = payload || {};
|
||||
const current = deliveryEditorAutomat.state.attachments ?? [];
|
||||
const idx = current.findIndex((a) => a.id === id);
|
||||
if (idx !== -1) {
|
||||
const next = [...current];
|
||||
next[idx] = { ...next[idx], ...fields };
|
||||
deliveryEditorAutomat.setState({ attachments: next, files: next });
|
||||
}
|
||||
},
|
||||
|
||||
removeAttachment(idOrIndex) {
|
||||
const current = deliveryEditorAutomat.state.attachments ?? [];
|
||||
let next;
|
||||
if (typeof idOrIndex === 'number') {
|
||||
next = current.filter((_, idx) => idx !== idOrIndex);
|
||||
} else {
|
||||
const id = typeof idOrIndex === 'string' ? idOrIndex : idOrIndex?.id;
|
||||
next = current.filter((a) => a.id !== id);
|
||||
}
|
||||
deliveryEditorAutomat.setState({ attachments: next, files: next });
|
||||
},
|
||||
|
||||
resetDelivery() {
|
||||
deliveryEditorAutomat.setState({
|
||||
...initialDelivery,
|
||||
});
|
||||
},
|
||||
|
||||
resetDeliveryEditor() {
|
||||
deliveryEditorAutomat.setState({
|
||||
...initialState,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setId,
|
||||
setDeliveryEntities,
|
||||
setDelivery,
|
||||
setDate,
|
||||
setDeliveryEntity,
|
||||
setPartyId,
|
||||
setExternalNumber,
|
||||
setNumber,
|
||||
setType,
|
||||
setLines,
|
||||
setLineitems,
|
||||
setNotes,
|
||||
addLineitem,
|
||||
addLine,
|
||||
updateLineitem,
|
||||
updateLine,
|
||||
removeLineitem,
|
||||
removeLine,
|
||||
addAttachment,
|
||||
setAttachments,
|
||||
updateAttachment,
|
||||
removeAttachment,
|
||||
resetDelivery,
|
||||
resetDeliveryEditor,
|
||||
} = deliveryEditorAutomat.actions;
|
||||
|
||||
// Selectors
|
||||
export const selectDeliveryId = (state = deliveryEditorAutomat.state) => state.deliveryId ?? state.id;
|
||||
export const selectDeliveryEntities = (state = deliveryEditorAutomat.state) => state.deliveryEntities;
|
||||
export const selectDeliveryDate = (state = deliveryEditorAutomat.state) => state.date;
|
||||
export const selectDeliveryEntity = (state = deliveryEditorAutomat.state) => state.deliveryEntity ?? state.partyId;
|
||||
export const selectDeliveryExternalNumber = (state = deliveryEditorAutomat.state) => state.externalNumber ?? state.external_number;
|
||||
export const selectDeliveryNumber = (state = deliveryEditorAutomat.state) => state.number;
|
||||
export const selectDeliveryType = (state = deliveryEditorAutomat.state) => state.type;
|
||||
export const selectDeliveryLineitems = (state = deliveryEditorAutomat.state) => state.lines ?? state.lineitems;
|
||||
export const selectDeliveryNotes = (state = deliveryEditorAutomat.state) => state.notes;
|
||||
export const selectDeliveryAttachments = (state = deliveryEditorAutomat.state) => state.attachments;
|
||||
|
||||
export default deliveryEditorAutomat;
|
||||
Reference in New Issue
Block a user