369 lines
11 KiB
JavaScript
369 lines
11 KiB
JavaScript
import { create } from "zustand";
|
|
import { devtools } from "zustand/middleware";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
const createElementByType = (type, count) => {
|
|
const baseId = uuidv4();
|
|
|
|
if (type === "text") {
|
|
return {
|
|
id: baseId,
|
|
type: "text",
|
|
x: 50 + count * 20,
|
|
y: 50 + count * 20,
|
|
text: "New Text",
|
|
fontSize: 18,
|
|
fontFamily: "Arial",
|
|
fontWeight: "normal",
|
|
color: "#000000",
|
|
align: "left",
|
|
bold: false,
|
|
italic: false,
|
|
underline: false,
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "image") {
|
|
return {
|
|
id: baseId,
|
|
type: "image",
|
|
x: 50 + count * 20,
|
|
y: 50 + count * 20,
|
|
width: 100,
|
|
height: 100,
|
|
src: null,
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "qrcode") {
|
|
return {
|
|
id: baseId,
|
|
type: "qrcode",
|
|
x: 50 + count * 20,
|
|
y: 50 + count * 20,
|
|
size: 100,
|
|
data: "https://example.com",
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "line") {
|
|
return {
|
|
id: baseId,
|
|
type: "line",
|
|
x: 50,
|
|
y: 100 + count * 20,
|
|
width: 400,
|
|
thickness: 2,
|
|
color: "#000000",
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "rectangle") {
|
|
return {
|
|
id: baseId,
|
|
type: "rectangle",
|
|
x: 50 + count * 20,
|
|
y: 50 + count * 20,
|
|
width: 200,
|
|
height: 100,
|
|
fill: "transparent",
|
|
stroke: "#000000",
|
|
strokeWidth: 2,
|
|
cornerRadius: 0,
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "summary") {
|
|
return {
|
|
id: baseId,
|
|
type: "summary",
|
|
x: 50,
|
|
y: 300 + count * 20,
|
|
fields: [
|
|
{ key: "items", label: "Items", visible: true, width: 150 },
|
|
{ key: "qty", label: "Qty", visible: true, width: 100 },
|
|
{ key: "amount", label: "Amount", visible: true, width: 150 },
|
|
],
|
|
fontSize: 14,
|
|
labelColor: "#000000",
|
|
valueColor: "#000000",
|
|
spacing: 10,
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
if (type === "table") {
|
|
return {
|
|
id: baseId,
|
|
type: "table",
|
|
x: 50,
|
|
y: 200 + count * 20,
|
|
width: 480,
|
|
columns: [
|
|
{ key: "sno", label: "S.No", width: 50, visible: true },
|
|
{ key: "item", label: "Item", width: 150, visible: true },
|
|
{ key: "hsn", label: "HSN", width: 60, visible: true },
|
|
{ key: "qty", label: "Qty", width: 50, visible: true },
|
|
{ key: "mrp", label: "MRP", width: 60, visible: true },
|
|
{ key: "rate", label: "Rate", width: 60, visible: true },
|
|
{ key: "dis", label: "Dis", width: 50, visible: true },
|
|
{ key: "tax", label: "Tax %", width: 60, visible: true },
|
|
{ key: "amt", label: "Amt", width: 70, visible: true },
|
|
],
|
|
sampleData: [
|
|
{ sno: "1", item: "ITEM 1", hsn: "01", qty: "2", mrp: "100", rate: "88", dis: "0", tax: "5", amt: "176" },
|
|
{ sno: "2", item: "ITEM 2", hsn: "02", qty: "1", mrp: "350", rate: "300", dis: "0", tax: "5", amt: "300" },
|
|
{ sno: "3", item: "ITEM 3", hsn: "03", qty: "1", mrp: "250", rate: "200", dis: "0", tax: "0", amt: "200" },
|
|
{ sno: "4", item: "ITEM 4", hsn: "04", qty: "1", mrp: "50", rate: "30", dis: "0", tax: "18", amt: "30" },
|
|
{ sno: "5", item: "ITEM 5", hsn: "05", qty: "1", mrp: "50", rate: "30", dis: "0", tax: "40", amt: "30" },
|
|
],
|
|
rowHeight: 30,
|
|
headerBg: "#f0f0f0",
|
|
borderColor: "#000000",
|
|
showSampleData: true,
|
|
rotation: 0,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const useEditorStore = create(
|
|
devtools((set, get) => ({
|
|
// State
|
|
elements: [],
|
|
selectedId: null,
|
|
clipboard: null,
|
|
history: [],
|
|
historyIndex: -1,
|
|
page: {
|
|
width: 794,
|
|
height: 1123,
|
|
name: "A4",
|
|
},
|
|
showLayers: true,
|
|
|
|
// Actions
|
|
addElement: (type) => {
|
|
const state = get();
|
|
const newElement = createElementByType(type, state.elements.length);
|
|
if (!newElement) return;
|
|
|
|
set((state) => ({
|
|
elements: [...state.elements, newElement],
|
|
selectedId: newElement.id,
|
|
history: [...state.history.slice(0, state.historyIndex + 1), state.elements],
|
|
historyIndex: state.historyIndex + 1,
|
|
}));
|
|
},
|
|
|
|
updateElement: (id, updates) => {
|
|
set((state) => ({
|
|
elements: state.elements.map((el) =>
|
|
el.id === id ? { ...el, ...updates } : el
|
|
),
|
|
}));
|
|
},
|
|
|
|
deleteElement: (id) => {
|
|
set((state) => ({
|
|
elements: state.elements.filter((el) => el.id !== id),
|
|
selectedId: state.selectedId === id ? null : state.selectedId,
|
|
}));
|
|
},
|
|
|
|
deleteSelected: () => {
|
|
const { selectedId, deleteElement } = get();
|
|
if (selectedId) deleteElement(selectedId);
|
|
},
|
|
|
|
setSelectedId: (id) => set({ selectedId: id }),
|
|
|
|
duplicateElement: (id) => {
|
|
const state = get();
|
|
const element = state.elements.find((el) => el.id === id);
|
|
if (!element) return;
|
|
|
|
const newElement = {
|
|
...element,
|
|
id: uuidv4(),
|
|
x: element.x + 20,
|
|
y: element.y + 20,
|
|
};
|
|
|
|
set((state) => ({
|
|
elements: [...state.elements, newElement],
|
|
selectedId: newElement.id,
|
|
}));
|
|
},
|
|
|
|
copyElement: () => {
|
|
const { selectedId, elements } = get();
|
|
const element = elements.find((el) => el.id === selectedId);
|
|
if (element) {
|
|
set({ clipboard: element });
|
|
}
|
|
},
|
|
|
|
pasteElement: () => {
|
|
const { clipboard } = get();
|
|
if (!clipboard) return;
|
|
|
|
const newElement = {
|
|
...clipboard,
|
|
id: uuidv4(),
|
|
x: clipboard.x + 20,
|
|
y: clipboard.y + 20,
|
|
};
|
|
|
|
set((state) => ({
|
|
elements: [...state.elements, newElement],
|
|
selectedId: newElement.id,
|
|
}));
|
|
},
|
|
|
|
moveElement: (id, direction) => {
|
|
const step = 10;
|
|
const deltas = {
|
|
up: { x: 0, y: -step },
|
|
down: { x: 0, y: step },
|
|
left: { x: -step, y: 0 },
|
|
right: { x: step, y: 0 },
|
|
};
|
|
|
|
const delta = deltas[direction];
|
|
if (!delta) return;
|
|
|
|
set((state) => ({
|
|
elements: state.elements.map((el) =>
|
|
el.id === id
|
|
? { ...el, x: el.x + delta.x, y: el.y + delta.y }
|
|
: el
|
|
),
|
|
}));
|
|
},
|
|
|
|
reorderColumns: (elementId, newColumns) => {
|
|
set((state) => ({
|
|
elements: state.elements.map((el) =>
|
|
el.id === elementId ? { ...el, columns: newColumns } : el
|
|
),
|
|
}));
|
|
},
|
|
|
|
toggleColumnVisibility: (elementId, columnKey) => {
|
|
set((state) => ({
|
|
elements: state.elements.map((el) => {
|
|
if (el.id === elementId && el.type === "table") {
|
|
return {
|
|
...el,
|
|
columns: el.columns.map((col) =>
|
|
col.key === columnKey
|
|
? { ...col, visible: !col.visible }
|
|
: col
|
|
),
|
|
};
|
|
}
|
|
return el;
|
|
}),
|
|
}));
|
|
},
|
|
|
|
updateColumnWidth: (elementId, columnKey, width) => {
|
|
set((state) => ({
|
|
elements: state.elements.map((el) => {
|
|
if (el.id === elementId && el.type === "table") {
|
|
return {
|
|
...el,
|
|
columns: el.columns.map((col) =>
|
|
col.key === columnKey ? { ...col, width } : col
|
|
),
|
|
};
|
|
}
|
|
return el;
|
|
}),
|
|
}));
|
|
},
|
|
|
|
updateColumnLabel: (elementId, columnKey, label) => {
|
|
set((state) => ({
|
|
elements: state.elements.map((el) => {
|
|
if (el.id === elementId && el.type === "table") {
|
|
return {
|
|
...el,
|
|
columns: el.columns.map((col) =>
|
|
col.key === columnKey ? { ...col, label } : col
|
|
),
|
|
};
|
|
}
|
|
return el;
|
|
}),
|
|
}));
|
|
},
|
|
|
|
// Undo/Redo
|
|
undo: () => {
|
|
const { historyIndex, history } = get();
|
|
if (historyIndex > 0) {
|
|
set({
|
|
elements: history[historyIndex - 1],
|
|
historyIndex: historyIndex - 1,
|
|
});
|
|
}
|
|
},
|
|
|
|
redo: () => {
|
|
const { historyIndex, history } = get();
|
|
if (historyIndex < history.length - 1) {
|
|
set({
|
|
elements: history[historyIndex + 1],
|
|
historyIndex: historyIndex + 1,
|
|
});
|
|
}
|
|
},
|
|
|
|
// Page settings
|
|
setPageSize: (size) => {
|
|
set({ page: { ...get().page, ...size } });
|
|
},
|
|
|
|
toggleLayers: () => {
|
|
set((state) => ({ showLayers: !state.showLayers }));
|
|
},
|
|
|
|
// Save/Load
|
|
exportTemplate: () => {
|
|
const { elements, page } = get();
|
|
return JSON.stringify({ elements, page }, null, 2);
|
|
},
|
|
|
|
importTemplate: (jsonString) => {
|
|
try {
|
|
const data = JSON.parse(jsonString);
|
|
set({
|
|
elements: data.elements || [],
|
|
page: data.page || get().page,
|
|
selectedId: null,
|
|
});
|
|
} catch (error) {
|
|
console.error("Invalid template JSON", error);
|
|
}
|
|
},
|
|
|
|
clearCanvas: () => {
|
|
set({
|
|
elements: [],
|
|
selectedId: null,
|
|
history: [],
|
|
historyIndex: -1,
|
|
});
|
|
},
|
|
}))
|
|
);
|
|
|
|
export default useEditorStore; |