Phase 10: field editor (FR-14)

เพิ่ม editor ต่อ template field — ผู้ดูแลเลือกรูปแบบช่องกรอกในฟอร์มสร้างเอกสารได้:
text → textbox/textarea, number/date → control ตาม type (presentation metadata
ล้วน — validation ยังยึด Type, ค่า field เป็น string ทั้ง flow, ไม่แตะ generate/sync/add-in)

- Backend: TemplateField.Editor (nullable, null = default ของ type) +
  migration AddTemplateFieldEditor + validate ตาม type ใน PUT templates
  (ค่าไม่รู้จัก/ไม่เข้ากับ type → 400, normalize-on-write, API คืน resolved เสมอ)
- Web: util fieldEditor.ts + editor select หน้าแก้ template (desktop+mobile,
  ซ่อนเมื่อ type มีทางเดียว) + ฟอร์มสร้างเอกสาร render ตาม editor +
  แก้ prefill re-generate ของ field date จาก ISO timestamp เต็ม (input[type=date]
  รับเฉพาะ yyyy-MM-dd เดิมแสดงว่าง)
- Docs: spec v6 (FR-14/AC-17) + plan Phase 10 + sub-plan field-editor.md +
  acceptance-review AC-17 + README/CLAUDE.md
- Tests: backend 60/60 (FieldEditorTests ใหม่ 8 กรณี) · web 46/46 + build ผ่าน

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Suphonchai Phoonsawat 2026-08-22 09:17:15 +07:00
parent 7b20f12ede
commit 9bb4a48c00
23 changed files with 865 additions and 45 deletions

View file

@ -30,11 +30,12 @@ public sealed class TemplateTests(IntegrationTestFixture fixture)
["employee_name", "position", "salary", "start_date"],
fields.Select(f => f.Key).ToList());
// defaults: label = tag, type = text, required = false (FR-9)
// defaults: label = tag, type = text, required = false (FR-9), editor = default ของ type (FR-14)
Assert.All(fields, f =>
{
Assert.Equal(f.Key, f.Label);
Assert.Equal("text", f.Type);
Assert.Equal("textbox", f.Editor);
Assert.False(f.IsRequired);
});
@ -85,6 +86,7 @@ public sealed class TemplateTests(IntegrationTestFixture fixture)
key = f.Key,
label = $"ป้ายกำกับ {f.Key}",
type = f.Key == "salary" ? "number" : f.Key == "start_date" ? "date" : "text",
editor = f.Key == "employee_name" ? "textarea" : null as string, // ส่งเฉพาะ text field (FR-14)
isRequired = f.Key == "employee_name",
sortOrder = fields.Count - i, // กลับลำดับ
}),
@ -99,6 +101,12 @@ public sealed class TemplateTests(IntegrationTestFixture fixture)
Assert.Equal("number", body.Fields.First(f => f.Key == "salary").Type);
Assert.Equal("date", body.Fields.First(f => f.Key == "start_date").Type);
Assert.True(body.Fields.First(f => f.Key == "employee_name").IsRequired);
// editor: ที่ส่งคือ textarea, ที่ไม่ส่ง resolve เป็น default ของ type (FR-14)
Assert.Equal("textarea", body.Fields.First(f => f.Key == "employee_name").Editor);
Assert.Equal("textbox", body.Fields.First(f => f.Key == "position").Editor);
Assert.Equal("number", body.Fields.First(f => f.Key == "salary").Editor);
Assert.Equal("date", body.Fields.First(f => f.Key == "start_date").Editor);
}
[Fact]