feat : update
CI / Verify (push) Successful in 1m6s
CI / Build immutable images and deploy (push) Successful in 2m49s

This commit is contained in:
2026-09-05 18:39:03 +07:00 Unverified
parent 5aa0ae80c1
commit 2429b9d319
9 changed files with 129 additions and 21 deletions
+25 -5
View File
@@ -13,6 +13,7 @@ import {
moveExtraSheetRow,
normalizeExtraSheetRichText,
updateExtraSheetCell,
updateExtraSheetCells,
} from "@/lib/guides/extra-sheet";
function idFactory() {
@@ -26,8 +27,8 @@ describe("Extra sheet", () => {
expect(extraSheetSchema.safeParse(sheet).success).toBe(true);
expect(sheet.columns.map((column) => column.label)).toEqual(["คอลัมน์ 1", "คอลัมน์ 2"]);
expect(extraSheetToMatrix(sheet)).toEqual([
[{ value: "" }, { value: "" }],
[{ value: "" }, { value: "" }],
[{ value: "", backgroundColor: undefined }, { value: "", backgroundColor: undefined }],
[{ value: "", backgroundColor: undefined }, { value: "", backgroundColor: undefined }],
]);
});
@@ -42,8 +43,8 @@ describe("Extra sheet", () => {
const moved = moveExtraSheetRow(moveExtraSheetColumn(sheet, 0, 1), 0, 1);
expect(extraSheetToMatrix(moved)).toEqual([
[{ value: "" }, { value: "C" }],
[{ value: "B" }, { value: "A" }],
[{ value: "", backgroundColor: undefined }, { value: "C", backgroundColor: undefined }],
[{ value: "B", backgroundColor: undefined }, { value: "A", backgroundColor: undefined }],
]);
});
@@ -60,7 +61,10 @@ describe("Extra sheet", () => {
const reduced = deleteExtraSheetColumn(expanded, 2);
expect(reduced.rows.every((row) => !(columnId in row.cells))).toBe(true);
expect(deleteExtraSheetRow(reduced, 2).rows).toHaveLength(2);
const secondRowId = reduced.rows[1].id;
const withoutFirstRow = deleteExtraSheetRow(reduced, 0);
expect(withoutFirstRow.rows).toHaveLength(2);
expect(withoutFirstRow.rows[0].id).toBe(secondRowId);
});
it("expands rows and columns when a pasted matrix is larger", () => {
@@ -102,4 +106,20 @@ describe("Extra sheet", () => {
"<p><strong>safe</strong></p>",
);
});
it("updates rich text and backgrounds across a cell selection", () => {
const sheet = createExtraSheet(idFactory());
const updated = updateExtraSheetCells(sheet, [
{ row: 0, column: 0 },
{ row: 1, column: 1 },
], { value: "<p>Shared</p>", backgroundColor: "#16A34A" });
expect(extraSheetToMatrix(updated)).toEqual([
[{ value: "<p>Shared</p>", backgroundColor: "#16A34A" }, { value: "", backgroundColor: undefined }],
[{ value: "", backgroundColor: undefined }, { value: "<p>Shared</p>", backgroundColor: "#16A34A" }],
]);
expect(extraSheetSchema.safeParse(updated).success).toBe(true);
expect(updateExtraSheetCells(updated, [{ row: 0, column: 0 }], { backgroundColor: null })
.rows[0].backgrounds).toEqual({});
});
});
+38 -3
View File
@@ -12,6 +12,7 @@ export const extraSheetSchema = z.strictObject({
rows: z.array(z.strictObject({
id: extraSheetId,
cells: z.record(z.string(), z.string().max(10_000)),
backgrounds: z.record(z.string(), z.string().regex(/^#[0-9A-Fa-f]{6}$/)).default({}),
})).min(1),
}).superRefine((sheet, context) => {
const columnIds = new Set(sheet.columns.map((column) => column.id));
@@ -27,7 +28,7 @@ export const extraSheetSchema = z.strictObject({
context.addIssue({ code: "custom", path: ["rows"], message: "รหัสแถวต้องไม่ซ้ำกัน" });
}
sheet.rows.forEach((row, rowIndex) => {
Object.keys(row.cells).forEach((columnId) => {
[...Object.keys(row.cells), ...Object.keys(row.backgrounds)].forEach((columnId) => {
if (!columnIds.has(columnId)) {
context.addIssue({
code: "custom",
@@ -41,8 +42,9 @@ export const extraSheetSchema = z.strictObject({
export type ExtraSheet = z.output<typeof extraSheetSchema>;
export type ExtraSheetCell = { value: string };
export type ExtraSheetCell = { value: string; backgroundColor?: string };
export type ExtraSheetMatrix = Array<Array<ExtraSheetCell | undefined>>;
type SheetPoint = { row: number; column: number };
export function nextExtraSheetColumnLabel(columns: ExtraSheet["columns"]): string {
const labels = new Set(columns.map((column) => column.label.toLocaleLowerCase()));
@@ -61,13 +63,17 @@ export function createExtraSheet(createId: (prefix: string) => string): ExtraShe
rows: [1, 2].map(() => ({
id: createId("extra-sheet-row"),
cells: Object.fromEntries(columns.map((column) => [column.id, ""])),
backgrounds: {},
})),
};
}
export function extraSheetToMatrix(sheet: ExtraSheet): ExtraSheetMatrix {
return sheet.rows.map((row) =>
sheet.columns.map((column) => ({ value: row.cells[column.id] ?? "" })),
sheet.columns.map((column) => ({
value: row.cells[column.id] ?? "",
backgroundColor: row.backgrounds[column.id],
})),
);
}
@@ -94,6 +100,11 @@ export function matrixToExtraSheet(
column.id,
String(matrix[rowIndex]?.[columnIndex]?.value ?? ""),
])),
backgrounds: Object.fromEntries(columns.flatMap((column, columnIndex) => {
const backgroundColor = matrix[rowIndex]?.[columnIndex]?.backgroundColor
?? sheet.rows[rowIndex]?.backgrounds[column.id];
return backgroundColor ? [[column.id, backgroundColor]] : [];
})),
}));
return { columns, rows };
}
@@ -115,6 +126,7 @@ export function addExtraSheetRow(sheet: ExtraSheet, id: string): ExtraSheet {
rows: [...sheet.rows, {
id,
cells: Object.fromEntries(sheet.columns.map((column) => [column.id, ""])),
backgrounds: {},
}],
};
}
@@ -152,6 +164,7 @@ export function deleteExtraSheetColumn(sheet: ExtraSheet, index: number): ExtraS
rows: sheet.rows.map((row) => ({
...row,
cells: Object.fromEntries(Object.entries(row.cells).filter(([key]) => key !== columnId)),
backgrounds: Object.fromEntries(Object.entries(row.backgrounds).filter(([key]) => key !== columnId)),
})),
};
}
@@ -178,6 +191,28 @@ export function updateExtraSheetCell(
};
}
export function updateExtraSheetCells(
sheet: ExtraSheet,
points: Iterable<SheetPoint>,
update: { value?: string; backgroundColor?: string | null },
): ExtraSheet {
const selected = new Set(Array.from(points, ({ row, column }) => `${row}:${column}`));
return {
...sheet,
rows: sheet.rows.map((row, rowIndex) => {
const cells = { ...row.cells };
const backgrounds = { ...row.backgrounds };
sheet.columns.forEach((column, columnIndex) => {
if (!selected.has(`${rowIndex}:${columnIndex}`)) return;
if (update.value !== undefined) cells[column.id] = update.value;
if (update.backgroundColor === null) delete backgrounds[column.id];
else if (update.backgroundColor !== undefined) backgrounds[column.id] = update.backgroundColor;
});
return { ...row, cells, backgrounds };
}),
};
}
export function normalizeExtraSheetRichText(sheet: ExtraSheet): ExtraSheet {
return {
...sheet,