161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
import * as z from "zod";
|
|
|
|
const extraSheetId = z.string().min(1).max(128);
|
|
|
|
export const extraSheetSchema = z.strictObject({
|
|
columns: z.array(z.strictObject({
|
|
id: extraSheetId,
|
|
label: z.string().trim().min(1).max(120),
|
|
})).min(1),
|
|
rows: z.array(z.strictObject({
|
|
id: extraSheetId,
|
|
cells: z.record(z.string(), z.string().max(10_000)),
|
|
})).min(1),
|
|
}).superRefine((sheet, context) => {
|
|
const columnIds = new Set(sheet.columns.map((column) => column.id));
|
|
if (columnIds.size !== sheet.columns.length) {
|
|
context.addIssue({ code: "custom", path: ["columns"], message: "รหัสคอลัมน์ต้องไม่ซ้ำกัน" });
|
|
}
|
|
const columnLabels = new Set(sheet.columns.map((column) => column.label.toLocaleLowerCase()));
|
|
if (columnLabels.size !== sheet.columns.length) {
|
|
context.addIssue({ code: "custom", path: ["columns"], message: "ชื่อคอลัมน์ต้องไม่ซ้ำกัน" });
|
|
}
|
|
const rowIds = new Set(sheet.rows.map((row) => row.id));
|
|
if (rowIds.size !== sheet.rows.length) {
|
|
context.addIssue({ code: "custom", path: ["rows"], message: "รหัสแถวต้องไม่ซ้ำกัน" });
|
|
}
|
|
sheet.rows.forEach((row, rowIndex) => {
|
|
Object.keys(row.cells).forEach((columnId) => {
|
|
if (!columnIds.has(columnId)) {
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["rows", rowIndex, "cells", columnId],
|
|
message: "ไม่พบคอลัมน์ของเซลล์นี้",
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
export type ExtraSheet = z.output<typeof extraSheetSchema>;
|
|
|
|
export type ExtraSheetCell = { value: string };
|
|
export type ExtraSheetMatrix = Array<Array<ExtraSheetCell | undefined>>;
|
|
|
|
export function nextExtraSheetColumnLabel(columns: ExtraSheet["columns"]): string {
|
|
const labels = new Set(columns.map((column) => column.label.toLocaleLowerCase()));
|
|
let index = columns.length + 1;
|
|
while (labels.has(`คอลัมน์ ${index}`.toLocaleLowerCase())) index += 1;
|
|
return `คอลัมน์ ${index}`;
|
|
}
|
|
|
|
export function createExtraSheet(createId: (prefix: string) => string): ExtraSheet {
|
|
const columns = [1, 2].map((index) => ({
|
|
id: createId("extra-sheet-column"),
|
|
label: `คอลัมน์ ${index}`,
|
|
}));
|
|
return {
|
|
columns,
|
|
rows: [1, 2].map(() => ({
|
|
id: createId("extra-sheet-row"),
|
|
cells: Object.fromEntries(columns.map((column) => [column.id, ""])),
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function extraSheetToMatrix(sheet: ExtraSheet): ExtraSheetMatrix {
|
|
return sheet.rows.map((row) =>
|
|
sheet.columns.map((column) => ({ value: row.cells[column.id] ?? "" })),
|
|
);
|
|
}
|
|
|
|
export function matrixToExtraSheet(
|
|
sheet: ExtraSheet,
|
|
matrix: ExtraSheetMatrix,
|
|
createId: (prefix: string) => string,
|
|
): ExtraSheet {
|
|
const columnCount = Math.max(
|
|
sheet.columns.length,
|
|
...matrix.map((row) => row.length),
|
|
);
|
|
const columns = [...sheet.columns];
|
|
while (columns.length < columnCount) {
|
|
columns.push({
|
|
id: createId("extra-sheet-column"),
|
|
label: nextExtraSheetColumnLabel(columns),
|
|
});
|
|
}
|
|
const rowCount = Math.max(sheet.rows.length, matrix.length);
|
|
const rows = Array.from({ length: rowCount }, (_, rowIndex) => ({
|
|
id: sheet.rows[rowIndex]?.id ?? createId("extra-sheet-row"),
|
|
cells: Object.fromEntries(columns.map((column, columnIndex) => [
|
|
column.id,
|
|
String(matrix[rowIndex]?.[columnIndex]?.value ?? ""),
|
|
])),
|
|
}));
|
|
return { columns, rows };
|
|
}
|
|
|
|
export function addExtraSheetColumn(
|
|
sheet: ExtraSheet,
|
|
label: string,
|
|
id: string,
|
|
): ExtraSheet {
|
|
return {
|
|
columns: [...sheet.columns, { id, label }],
|
|
rows: sheet.rows.map((row) => ({ ...row, cells: { ...row.cells, [id]: "" } })),
|
|
};
|
|
}
|
|
|
|
export function addExtraSheetRow(sheet: ExtraSheet, id: string): ExtraSheet {
|
|
return {
|
|
...sheet,
|
|
rows: [...sheet.rows, {
|
|
id,
|
|
cells: Object.fromEntries(sheet.columns.map((column) => [column.id, ""])),
|
|
}],
|
|
};
|
|
}
|
|
|
|
export function moveExtraSheetColumn(
|
|
sheet: ExtraSheet,
|
|
from: number,
|
|
to: number,
|
|
): ExtraSheet {
|
|
if (to < 0 || to >= sheet.columns.length || from === to) return sheet;
|
|
const columns = [...sheet.columns];
|
|
const [column] = columns.splice(from, 1);
|
|
columns.splice(to, 0, column);
|
|
return { ...sheet, columns };
|
|
}
|
|
|
|
export function moveExtraSheetRow(
|
|
sheet: ExtraSheet,
|
|
from: number,
|
|
to: number,
|
|
): ExtraSheet {
|
|
if (to < 0 || to >= sheet.rows.length || from === to) return sheet;
|
|
const rows = [...sheet.rows];
|
|
const [row] = rows.splice(from, 1);
|
|
rows.splice(to, 0, row);
|
|
return { ...sheet, rows };
|
|
}
|
|
|
|
export function deleteExtraSheetColumn(sheet: ExtraSheet, index: number): ExtraSheet {
|
|
if (sheet.columns.length === 1) return sheet;
|
|
const columnId = sheet.columns[index]?.id;
|
|
if (!columnId) return sheet;
|
|
return {
|
|
columns: sheet.columns.filter((_, columnIndex) => columnIndex !== index),
|
|
rows: sheet.rows.map((row) => ({
|
|
...row,
|
|
cells: Object.fromEntries(Object.entries(row.cells).filter(([key]) => key !== columnId)),
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function deleteExtraSheetRow(sheet: ExtraSheet, index: number): ExtraSheet {
|
|
if (sheet.rows.length === 1) return sheet;
|
|
return { ...sheet, rows: sheet.rows.filter((_, rowIndex) => rowIndex !== index) };
|
|
}
|