889 lines
30 KiB
Markdown
889 lines
30 KiB
Markdown
# Prompt: ตรวจสอบและแก้ไขสถานะไม่อัปเดตทันทีหลังเจ้าหน้าที่กด “ส่งตรวจสอบ” จากปุ่ม Action
|
|
|
|
## บทบาท
|
|
|
|
ให้คุณทำหน้าที่เป็น Senior Full-Stack Developer และ Code Reviewer ตรวจสอบ Flow การเปลี่ยนสถานะของประวัติการอบรมในหน้าของเจ้าหน้าที่ ตั้งแต่ปุ่ม Action ไปจนถึงการอัปเดตข้อมูลบนหน้ารายการ
|
|
|
|
ต้องตรวจสอบโค้ดจริงใน Repository ก่อนแก้ไข ห้ามคาดเดา Query Key, API Route, Field, Status, State Management หรือ Data Fetching Library ขึ้นมาเอง
|
|
|
|
---
|
|
|
|
# ปัญหา
|
|
|
|
ในหน้าประวัติการอบรมของเจ้าหน้าที่ เมื่อกดคำสั่ง:
|
|
|
|
```text
|
|
ส่งตรวจสอบ
|
|
```
|
|
|
|
จากปุ่ม Action ของรายการ ระบบสามารถเปลี่ยนสถานะใน Backend หรือ Database ได้สำเร็จ แต่สถานะบนหน้ารายการยังไม่เปลี่ยนทันที
|
|
|
|
ผู้ใช้งานต้องกด Refresh Browser เอง จึงจะเห็นสถานะใหม่
|
|
|
|
ตัวอย่างพฤติกรรมปัจจุบัน:
|
|
|
|
```text
|
|
สถานะเดิม: ฉบับร่าง
|
|
กด Action → ส่งตรวจสอบ
|
|
ระบบแจ้งว่าสำเร็จ
|
|
สถานะในตารางยังเป็น: ฉบับร่าง
|
|
|
|
เมื่อ Refresh Browser
|
|
สถานะเปลี่ยนเป็น: รอตรวจสอบ
|
|
```
|
|
|
|
---
|
|
|
|
# เป้าหมาย
|
|
|
|
หลังเจ้าหน้าที่กด “ส่งตรวจสอบ” และระบบดำเนินการสำเร็จ ต้อง:
|
|
|
|
1. อัปเดตสถานะรายการบนหน้าจอทันที
|
|
2. ไม่ต้อง Refresh Browser
|
|
3. Refetch หรืออัปเดต Cache ของหน้ารายการอย่างถูกต้อง
|
|
4. อัปเดตจำนวนรายการและ Pagination ให้ตรงกับข้อมูลล่าสุด
|
|
5. หากหน้าปัจจุบันกรองเฉพาะสถานะฉบับร่าง รายการที่ส่งตรวจสอบแล้วต้องหายออกจากตารางทันที
|
|
6. รายการต้องไปปรากฏในสถานะรอตรวจสอบเมื่อเปิด Filter หรือหน้าที่เกี่ยวข้อง
|
|
7. ไม่กระทบ Search, Filter, Sorting, Pagination, Permission และ Workflow เดิม
|
|
|
|
---
|
|
|
|
# ขอบเขตการตรวจสอบ
|
|
|
|
ตรวจสอบ Flow ทั้งหมด:
|
|
|
|
```text
|
|
Action Menu
|
|
→ Confirmation Dialog
|
|
→ Mutation / Server Action / API Request
|
|
→ Backend Status Update
|
|
→ Database Commit
|
|
→ Response
|
|
→ Cache Invalidation / Refetch
|
|
→ Table Data
|
|
→ Status Badge
|
|
→ Pagination / Count
|
|
```
|
|
|
|
ค้นหาไฟล์และ Component ที่เกี่ยวข้อง เช่น:
|
|
|
|
```text
|
|
TrainingRecordActionMenu
|
|
TrainingRecordCellAction
|
|
DraftTrainingRecordAction
|
|
SubmitForReviewAction
|
|
PendingReviewActionMenu
|
|
TrainingRecordTable
|
|
TrainingRecordList
|
|
TrainingRecordStatusBadge
|
|
```
|
|
|
|
รวมถึง Hook หรือ Function เช่น:
|
|
|
|
```text
|
|
useSubmitTrainingRecord
|
|
useSubmitForReview
|
|
useUpdateTrainingRecordStatus
|
|
submitTrainingRecord
|
|
updateTrainingRecordStatus
|
|
```
|
|
|
|
ชื่อจริงอาจแตกต่าง ให้ตรวจสอบจาก Repository
|
|
|
|
---
|
|
|
|
# Phase 1: ตรวจสอบหน้ารายการและ Data Fetching
|
|
|
|
ตรวจสอบว่าหน้ารายการประวัติการอบรมใช้วิธีดึงข้อมูลแบบใด เช่น:
|
|
|
|
- TanStack Query / React Query
|
|
- SWR
|
|
- Server Component
|
|
- Server Action
|
|
- `router.refresh()`
|
|
- Local State
|
|
- TanStack Table
|
|
- URL Search Params
|
|
|
|
ระบุให้ได้ว่า Source of Truth ของข้อมูลในตารางคืออะไร
|
|
|
|
ตัวอย่างสิ่งที่ต้องตรวจสอบ:
|
|
|
|
```ts
|
|
useQuery({
|
|
queryKey: ["training-records", filters],
|
|
queryFn: ...
|
|
});
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```ts
|
|
useSWR(`/api/training-records?${queryString}`, fetcher);
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```tsx
|
|
const data = await getTrainingRecords(searchParams);
|
|
```
|
|
|
|
จากนั้นตรวจสอบว่าหลัง Mutation สำเร็จ มีการสั่งอัปเดต Source of Truth นั้นหรือไม่
|
|
|
|
---
|
|
|
|
# Phase 2: ตรวจสอบ Mutation ของปุ่ม “ส่งตรวจสอบ”
|
|
|
|
ตรวจสอบ Handler ที่ถูกเรียกเมื่อกด Action เช่น:
|
|
|
|
```ts
|
|
handleSubmitForReview;
|
|
submitForReviewMutation;
|
|
onSubmitForReview;
|
|
handleStatusChange;
|
|
```
|
|
|
|
ตรวจสอบประเด็นต่อไปนี้:
|
|
|
|
1. มีการ `await` API หรือ Server Action จริงหรือไม่
|
|
2. Toast แสดงก่อน API สำเร็จหรือไม่
|
|
3. `router.refresh()` ถูกเรียกก่อน Mutation เสร็จหรือไม่
|
|
4. มี `onSuccess` หรือ Success Callback หรือไม่
|
|
5. มีการ Invalidate Query หรือ Revalidate Path หรือไม่
|
|
6. Query Key ที่ Invalidate ตรงกับหน้ารายการจริงหรือไม่
|
|
7. มีการอัปเดต Local State หรือ Table Data หรือไม่
|
|
8. Action Menu ปิดแล้วใช้ `row.original` ชุดเดิมต่อหรือไม่
|
|
9. Response จาก API มีสถานะใหม่หรือไม่
|
|
10. มี Error ถูก Ignore หรือไม่
|
|
|
|
---
|
|
|
|
# สาเหตุที่ต้องตรวจสอบเป็นพิเศษ
|
|
|
|
## 1. Mutation สำเร็จแต่ไม่ได้ Invalidate Query
|
|
|
|
ตัวอย่างปัญหา:
|
|
|
|
```ts
|
|
const mutation = useMutation({
|
|
mutationFn: submitForReview,
|
|
});
|
|
```
|
|
|
|
แต่ไม่มี:
|
|
|
|
```ts
|
|
queryClient.invalidateQueries(...)
|
|
```
|
|
|
|
ผลคือ Backend เปลี่ยนสถานะแล้ว แต่ Table ยังใช้ Cache เดิม
|
|
|
|
---
|
|
|
|
## 2. Query Key ไม่ตรงกัน
|
|
|
|
ตัวอย่าง:
|
|
|
|
หน้ารายการใช้:
|
|
|
|
```ts
|
|
["training-records", filters];
|
|
```
|
|
|
|
แต่ Action Invalidate:
|
|
|
|
```ts
|
|
["training-record"];
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```ts
|
|
["training-records", id];
|
|
```
|
|
|
|
ซึ่งไม่ครอบคลุม Query List
|
|
|
|
ให้ตรวจสอบ Query Key Factory หรือ Constant กลาง หากระบบมีอยู่แล้ว เช่น:
|
|
|
|
```ts
|
|
trainingRecordKeys.all;
|
|
trainingRecordKeys.lists();
|
|
trainingRecordKeys.list(filters);
|
|
trainingRecordKeys.detail(id);
|
|
```
|
|
|
|
ควรใช้ Key กลางแทน String ที่เขียนซ้ำ
|
|
|
|
---
|
|
|
|
## 3. ใช้ `router.refresh()` ไม่ตรงกับ Data Fetching Pattern
|
|
|
|
หาก Table ใช้ React Query หรือ SWR การเรียกเพียง:
|
|
|
|
```ts
|
|
router.refresh();
|
|
```
|
|
|
|
อาจไม่ทำให้ Client Cache เปลี่ยน
|
|
|
|
ในกรณีนี้ต้องใช้ Invalidate หรือ Mutate ของ Library ที่ใช้อยู่จริง
|
|
|
|
หากข้อมูลมาจาก Server Component และมี Cache ฝั่ง Server ต้องตรวจสอบ:
|
|
|
|
```ts
|
|
revalidatePath(...)
|
|
revalidateTag(...)
|
|
router.refresh()
|
|
```
|
|
|
|
ให้ใช้เท่าที่จำเป็นตามโครงสร้างจริง ห้ามเพิ่มทุกวิธีพร้อมกันโดยไม่มีเหตุผล
|
|
|
|
---
|
|
|
|
## 4. Refresh ถูกเรียกก่อน Backend Update เสร็จ
|
|
|
|
ตัวอย่างที่เสี่ยง:
|
|
|
|
```ts
|
|
startTransition(() => {
|
|
submitForReview(id);
|
|
router.refresh();
|
|
});
|
|
```
|
|
|
|
หาก `submitForReview` เป็น Async แต่ไม่ได้รอ ระบบอาจ Refresh ด้วยข้อมูลเก่าก่อน Database Commit
|
|
|
|
ต้องเปลี่ยนเป็น Flow ที่รอผลสำเร็จก่อน:
|
|
|
|
```ts
|
|
await submitForReview(id);
|
|
await refreshListData();
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Invalidate เฉพาะ Query ที่แคบเกินไป
|
|
|
|
หากหน้ารายการมีหลาย Filter เช่น:
|
|
|
|
```text
|
|
status=draft
|
|
status=pending
|
|
status=approved
|
|
all statuses
|
|
```
|
|
|
|
การ Invalidate เฉพาะ Query Draft อาจทำให้ Query Pending หรือ All Status ยังเก็บ Cache เก่า
|
|
|
|
ควรตรวจสอบว่า Query Key Hierarchy รองรับการ Invalidate List ทั้งหมด เช่น:
|
|
|
|
```ts
|
|
queryClient.invalidateQueries({
|
|
queryKey: trainingRecordKeys.lists(),
|
|
});
|
|
```
|
|
|
|
หรือ Key ต้นทางที่ครอบคลุมรายการทั้งหมด
|
|
|
|
---
|
|
|
|
# แนวทางการแก้ไขตาม Data Fetching Library
|
|
|
|
## กรณีใช้ TanStack Query / React Query
|
|
|
|
ให้ใช้ `useQueryClient()` และ Invalidate Query List หลัง Mutation สำเร็จ
|
|
|
|
ตัวอย่างแนวคิด:
|
|
|
|
```ts
|
|
const queryClient = useQueryClient();
|
|
|
|
const submitForReviewMutation = useMutation({
|
|
mutationFn: submitForReview,
|
|
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries({
|
|
queryKey: trainingRecordKeys.lists(),
|
|
});
|
|
|
|
toast.success("ส่งตรวจสอบเรียบร้อยแล้ว");
|
|
},
|
|
|
|
onError: (error) => {
|
|
toast.error(getErrorMessage(error));
|
|
},
|
|
});
|
|
```
|
|
|
|
หากระบบยังไม่มี Query Key Factory ให้พิจารณาใช้ Key เดิมให้ตรงกันก่อน ห้าม Refactor ใหญ่เกินขอบเขตโดยไม่จำเป็น
|
|
|
|
หากมี Detail Query ที่แสดงสถานะด้วย ให้ Invalidate เพิ่มเฉพาะที่เกี่ยวข้อง เช่น:
|
|
|
|
```ts
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({
|
|
queryKey: trainingRecordKeys.lists(),
|
|
}),
|
|
queryClient.invalidateQueries({
|
|
queryKey: trainingRecordKeys.detail(trainingRecordId),
|
|
}),
|
|
]);
|
|
```
|
|
|
|
---
|
|
|
|
## กรณีใช้ SWR
|
|
|
|
ตรวจสอบ Key จริงของหน้ารายการ ซึ่งอาจมี Query String เช่น:
|
|
|
|
```text
|
|
/api/training-records?page=1&status=draft
|
|
```
|
|
|
|
ให้ใช้ `mutate` ที่ครอบคลุม Key รายการทั้งหมดตาม Pattern เดิม
|
|
|
|
ตัวอย่างแนวคิด:
|
|
|
|
```ts
|
|
await mutate(
|
|
(key) => typeof key === "string" && key.startsWith("/api/training-records"),
|
|
);
|
|
```
|
|
|
|
หรือใช้ Key Factory กลาง หากมีอยู่แล้ว
|
|
|
|
ห้ามเรียก `mutate("/api/training-records")` หาก Key จริงมี Query String และไม่เกิดการ Match
|
|
|
|
---
|
|
|
|
## กรณีใช้ Server Component / Server Action
|
|
|
|
ตรวจสอบว่า Mutation ฝั่ง Server เรียก:
|
|
|
|
```ts
|
|
revalidatePath("/dashboard/training-records");
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```ts
|
|
revalidateTag("training-records");
|
|
```
|
|
|
|
จากนั้นฝั่ง Client อาจต้องเรียก:
|
|
|
|
```ts
|
|
router.refresh();
|
|
```
|
|
|
|
หลัง Server Action สำเร็จ
|
|
|
|
ตัวอย่างแนวคิด:
|
|
|
|
```ts
|
|
const result = await submitForReviewAction(id);
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message);
|
|
}
|
|
|
|
router.refresh();
|
|
toast.success("ส่งตรวจสอบเรียบร้อยแล้ว");
|
|
```
|
|
|
|
ต้องตรวจสอบให้แน่ใจว่า `router.refresh()` ถูกเรียกหลัง Action สำเร็จเท่านั้น
|
|
|
|
---
|
|
|
|
# การอัปเดตแบบ Optimistic
|
|
|
|
สามารถใช้ Optimistic Update ได้ หาก Pattern เดิมของระบบรองรับและ Type ปลอดภัย
|
|
|
|
แต่สำหรับกรณีสถานะเปลี่ยนแล้วรายการอาจต้อง:
|
|
|
|
- หายจาก Filter Draft
|
|
- ไปอยู่ Filter Pending
|
|
- เปลี่ยน Count
|
|
- เปลี่ยน Sorting
|
|
- เพิ่ม submitted_at
|
|
- เพิ่ม submitted_by
|
|
- สร้าง Audit Log
|
|
- สร้าง Notification
|
|
|
|
แนะนำให้ใช้:
|
|
|
|
```text
|
|
Mutation สำเร็จ
|
|
→ Invalidate
|
|
→ Refetch จาก Server
|
|
```
|
|
|
|
เป็นแนวทางหลัก เพื่อให้ข้อมูลตรงกับ Backend
|
|
|
|
หากต้องการ Optimistic Update ให้ทำร่วมกับ Invalidate ภายหลัง และต้องมี Rollback เมื่อเกิด Error
|
|
|
|
---
|
|
|
|
# การจัดการรายการที่ถูกกรองตามสถานะ
|
|
|
|
หากหน้าปัจจุบันเลือก Filter:
|
|
|
|
```text
|
|
สถานะ = ฉบับร่าง
|
|
```
|
|
|
|
เมื่อส่งตรวจสอบสำเร็จ รายการนั้นต้องหายจากตาราง เพราะสถานะใหม่ไม่ตรงกับ Filter ปัจจุบัน
|
|
|
|
ระบบต้องอัปเดต:
|
|
|
|
- Rows
|
|
- Total Count
|
|
- Page Count
|
|
- Empty State
|
|
- Pagination
|
|
|
|
ตัวอย่างกรณี:
|
|
|
|
```text
|
|
หน้า 2 มีรายการเดียว
|
|
ส่งตรวจสอบสำเร็จ
|
|
รายการหาย
|
|
หน้า 2 ไม่มีข้อมูล
|
|
```
|
|
|
|
ให้ตรวจสอบว่าระบบควร:
|
|
|
|
- กลับหน้า 1 หรือ
|
|
- ลดไปหน้าก่อนหน้า
|
|
|
|
ตาม Pagination Pattern เดิมของระบบ
|
|
|
|
ห้ามปล่อยให้ผู้ใช้ค้างอยู่บนหน้าว่างโดย Count และ Page ไม่ตรงกัน
|
|
|
|
---
|
|
|
|
# การ Reset หรือปรับ Pagination
|
|
|
|
หลังรายการถูกเปลี่ยนสถานะและหายจาก Filter ปัจจุบัน ให้ตรวจสอบ Pagination
|
|
|
|
หากข้อมูลหน้าปัจจุบันหมด ต้องปรับ Page ให้ถูกต้องตาม Pattern เดิม เช่น:
|
|
|
|
```ts
|
|
if (currentPage > newTotalPages) {
|
|
setPage(Math.max(newTotalPages, 1));
|
|
}
|
|
```
|
|
|
|
หากใช้ URL Search Params ต้องอัปเดต Parameter อย่างปลอดภัย
|
|
|
|
ห้าม Reset หน้า 1 ทุกครั้งโดยไม่มีเหตุผล หากระบบเดิมมีพฤติกรรมรักษาหน้าปัจจุบัน
|
|
|
|
---
|
|
|
|
# การอัปเดต Count หรือ Tab Badge
|
|
|
|
หากหน้าของเจ้าหน้าที่มี:
|
|
|
|
- จำนวนฉบับร่าง
|
|
- จำนวนรอตรวจสอบ
|
|
- Status Tabs
|
|
- Summary Cards
|
|
- Badge จำนวนรายการ
|
|
|
|
ต้อง Invalidate หรือ Refetch ข้อมูลสรุปที่เกี่ยวข้องด้วย
|
|
|
|
ตัวอย่าง Query Key ที่อาจต้องอัปเดต:
|
|
|
|
```text
|
|
training-records
|
|
training-record-counts
|
|
training-record-summary
|
|
dashboard-summary
|
|
notifications
|
|
```
|
|
|
|
ให้ตรวจสอบจากโค้ดจริงว่ามี Query ใดได้รับผลกระทบ
|
|
|
|
ห้าม Invalidate Query ทั้งระบบแบบกว้างเกินจำเป็น
|
|
|
|
---
|
|
|
|
# Loading และ Double Submit
|
|
|
|
ระหว่างส่งตรวจสอบ:
|
|
|
|
- Disable Action ที่กำลังกด
|
|
- แสดง Loading State
|
|
- ป้องกัน Double Click
|
|
- ป้องกัน Request ซ้ำ
|
|
- ไม่ปิด Dialog ก่อนเริ่ม Request หาก Pattern เดิมไม่รองรับ
|
|
- ไม่แสดง Toast สำเร็จก่อน Response สำเร็จ
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
<Button
|
|
disabled={submitForReviewMutation.isPending}
|
|
onClick={() => submitForReviewMutation.mutate(record.id)}
|
|
>
|
|
{submitForReviewMutation.isPending ? "กำลังส่ง..." : "ยืนยันส่งตรวจสอบ"}
|
|
</Button>
|
|
```
|
|
|
|
หากเป็น Action Menu หลายแถว ต้องพิจารณา Pending State แบบแยกตาม Record ID เพื่อไม่ให้ทุกแถว Disabled โดยไม่จำเป็น
|
|
|
|
---
|
|
|
|
# Error Handling
|
|
|
|
หากส่งตรวจสอบไม่สำเร็จ:
|
|
|
|
- ต้องไม่เปลี่ยนสถานะบน UI เป็นสถานะใหม่
|
|
- ต้องไม่ลบรายการออกจากตาราง
|
|
- ต้องไม่ Invalidate แบบทำให้ผู้ใช้เข้าใจผิด
|
|
- แสดง Error Message ที่เหมาะสม
|
|
- ปิดหรือเปิด Dialog ตาม Pattern เดิม
|
|
- ผู้ใช้สามารถลองใหม่ได้
|
|
|
|
หากใช้ Optimistic Update ต้อง Rollback Cache
|
|
|
|
---
|
|
|
|
# Permission และ Business Rule
|
|
|
|
ห้ามเปลี่ยน:
|
|
|
|
- สิทธิ์การส่งตรวจสอบ
|
|
- สถานะที่อนุญาตให้ส่งตรวจสอบ
|
|
- Workflow Transition
|
|
- Validation
|
|
- Audit Log
|
|
- Notification
|
|
- Approval Flow
|
|
- สิทธิ์ของ Employee, Staff, Manager, HRD หรือ Admin
|
|
- API Authorization
|
|
|
|
การแก้ไขครั้งนี้ต้องแก้เฉพาะการ Synchronize ข้อมูลหลัง Mutation สำเร็จ
|
|
|
|
หากพบว่า Backend อนุญาต Status Transition ผิด ต้องรายงานแยก ไม่ควรเปลี่ยน Business Rule โดยอัตโนมัติ
|
|
|
|
---
|
|
|
|
# ตรวจสอบ API Response
|
|
|
|
ตรวจสอบว่า API หรือ Server Action คืนผลลัพธ์ที่ชัดเจน เช่น:
|
|
|
|
```ts
|
|
{
|
|
success: true,
|
|
data: {
|
|
id: "...",
|
|
status: "pending",
|
|
updatedAt: "...",
|
|
},
|
|
}
|
|
```
|
|
|
|
หาก Response เดิมเพียงพอ ไม่ต้องเปลี่ยน Structure
|
|
|
|
หาก Response ไม่ชัดเจนจน Client ไม่ทราบว่าสำเร็จหรือไม่ ให้ปรับตามมาตรฐาน API เดิมของระบบ โดยหลีกเลี่ยง Breaking Change
|
|
|
|
---
|
|
|
|
# Test Cases
|
|
|
|
## Test Case 1: ส่งตรวจสอบสำเร็จจากรายการทั้งหมด
|
|
|
|
1. เปิดหน้าประวัติการอบรมของเจ้าหน้าที่
|
|
2. เลือกรายการสถานะฉบับร่าง
|
|
3. กด Action
|
|
4. กดส่งตรวจสอบ
|
|
5. ยืนยันคำสั่ง
|
|
6. ตรวจสอบ Success Message
|
|
7. ตรวจสอบว่าสถานะเปลี่ยนทันทีโดยไม่ Refresh Browser
|
|
8. ตรวจสอบข้อมูลจาก API หรือ Network ว่ามีการ Refetch
|
|
|
|
---
|
|
|
|
## Test Case 2: ส่งตรวจสอบขณะ Filter Draft
|
|
|
|
1. เลือก Filter สถานะฉบับร่าง
|
|
2. ส่งรายการหนึ่งไปตรวจสอบ
|
|
3. รายการต้องหายจากตารางทันที
|
|
4. Total Count ต้องลดลง
|
|
5. Page Count ต้องถูกต้อง
|
|
6. เมื่อเลือก Filter รอตรวจสอบ รายการต้องปรากฏ
|
|
|
|
---
|
|
|
|
## Test Case 3: ส่งตรวจสอบจากหน้ารวมทุกสถานะ
|
|
|
|
1. เปิดรายการทุกสถานะ
|
|
2. ส่ง Draft ไปตรวจสอบ
|
|
3. Row เดิมต้องยังอยู่ในตาราง หากตรงกับ Filter
|
|
4. Status Badge ต้องเปลี่ยนเป็นรอตรวจสอบ
|
|
5. Action Menu ต้องเปลี่ยนตามสถานะใหม่
|
|
|
|
---
|
|
|
|
## Test Case 4: ใช้ร่วมกับ Search
|
|
|
|
1. ค้นหารายการ
|
|
2. ส่งตรวจสอบจากผลการค้นหา
|
|
3. ตรวจสอบว่าสถานะอัปเดต
|
|
4. Search Value ต้องไม่หาย
|
|
5. ข้อมูลต้องยังตรงกับคำค้นหา
|
|
|
|
---
|
|
|
|
## Test Case 5: ใช้ร่วมกับ Pagination
|
|
|
|
1. ไปหน้าที่มากกว่า 1
|
|
2. ส่งรายการตรวจสอบ
|
|
3. ตรวจสอบ Count และ Page
|
|
4. หากหน้าปัจจุบันไม่มีข้อมูล ต้องปรับหน้าอย่างถูกต้อง
|
|
5. ห้ามเกิดหน้าว่างที่ Pagination ผิด
|
|
|
|
---
|
|
|
|
## Test Case 6: API Error
|
|
|
|
1. จำลอง API Error
|
|
2. กดส่งตรวจสอบ
|
|
3. ต้องแสดง Error
|
|
4. สถานะเดิมต้องคงอยู่
|
|
5. รายการต้องไม่หาย
|
|
6. ไม่ต้อง Refresh Browser เพื่อกู้สถานะ
|
|
|
|
---
|
|
|
|
## Test Case 7: Double Click
|
|
|
|
1. กดส่งตรวจสอบซ้ำอย่างรวดเร็ว
|
|
2. ต้องเกิด Request เดียว
|
|
3. ไม่สร้าง Audit Log หรือ Notification ซ้ำ
|
|
4. สถานะต้องเปลี่ยนเพียงครั้งเดียว
|
|
|
|
---
|
|
|
|
## Test Case 8: Query Key
|
|
|
|
1. ตรวจสอบ Query Key ของหน้ารายการ
|
|
2. ตรวจสอบ Query Key ที่ Invalidate
|
|
3. ต้อง Match กันตาม Hierarchy
|
|
4. ตรวจสอบ Filter หลายรูปแบบ
|
|
5. ทุก Query List ที่เกี่ยวข้องต้องอัปเดต
|
|
|
|
---
|
|
|
|
## Test Case 9: Refresh Browser หลังแก้ไข
|
|
|
|
1. ส่งตรวจสอบ
|
|
2. ตรวจสอบสถานะทันที
|
|
3. Refresh Browser
|
|
4. สถานะหลัง Refresh ต้องตรงกับก่อน Refresh
|
|
5. ไม่มีข้อมูลย้อนกลับเป็นสถานะเดิม
|
|
|
|
---
|
|
|
|
## Test Case 10: Permission
|
|
|
|
1. Login ด้วย Role ที่มีสิทธิ์ส่งตรวจสอบ
|
|
2. ตรวจสอบว่าส่งได้ตามเดิม
|
|
3. Login ด้วย Role ที่ไม่มีสิทธิ์
|
|
4. ตรวจสอบว่า Action ไม่แสดงหรือถูกปฏิเสธตามเดิม
|
|
5. การแก้ Cache ต้องไม่เปิดเผยข้อมูลของผู้ใช้อื่น
|
|
|
|
---
|
|
|
|
# Automated Test
|
|
|
|
หาก Repository มี Test Framework ให้เพิ่มหรือปรับปรุง Test ตาม Pattern เดิม
|
|
|
|
อย่างน้อยควรครอบคลุม:
|
|
|
|
```text
|
|
mutation success
|
|
mutation error
|
|
list query invalidation
|
|
filtered draft list update
|
|
all-status list update
|
|
pagination count update
|
|
double-submit prevention
|
|
permission unchanged
|
|
```
|
|
|
|
หากใช้ TanStack Query สามารถ Test ได้ว่า `invalidateQueries` ถูกเรียกด้วย Query Key ที่ถูกต้อง
|
|
|
|
หากใช้ SWR ให้ Test ว่า `mutate` Match Key ที่มี Query String
|
|
|
|
หากใช้ Server Action ให้ Test ว่า Revalidation และ Refresh เกิดหลัง Success
|
|
|
|
---
|
|
|
|
# Validation ก่อนส่งงาน
|
|
|
|
รันคำสั่งตาม `package.json` และ Repository จริง เช่น:
|
|
|
|
```bash
|
|
pnpm typecheck
|
|
pnpm lint
|
|
pnpm test
|
|
pnpm build
|
|
```
|
|
|
|
ห้ามแก้ Type Error ด้วย:
|
|
|
|
```ts
|
|
any
|
|
@ts-ignore
|
|
@ts-expect-error
|
|
eslint-disable
|
|
```
|
|
|
|
เว้นแต่มีเหตุผลจำเป็นและอธิบายไว้ในรายงาน
|
|
|
|
---
|
|
|
|
# Acceptance Criteria
|
|
|
|
งานถือว่าเสร็จเมื่อ:
|
|
|
|
- กดส่งตรวจสอบแล้วสถานะอัปเดตทันที
|
|
- ไม่ต้อง Refresh Browser
|
|
- Mutation รอ Backend สำเร็จก่อนอัปเดต UI
|
|
- ใช้ Cache Invalidation หรือ Refetch ตาม Data Fetching Pattern จริง
|
|
- Query Key ที่ Invalidate ตรงกับหน้ารายการ
|
|
- รายการ Draft หายจาก Draft Filter ทันที
|
|
- รายการปรากฏใน Pending Filter
|
|
- รายการในหน้ารวมเปลี่ยน Status Badge ทันที
|
|
- Action Menu เปลี่ยนตามสถานะใหม่
|
|
- Total Count และ Page Count ถูกต้อง
|
|
- Pagination ไม่ค้างหน้าที่ไม่มีข้อมูล
|
|
- Search, Filter และ Sorting ยังคงอยู่
|
|
- ป้องกัน Double Submit
|
|
- Error ไม่ทำให้ UI แสดงสถานะผิด
|
|
- Permission และ Workflow ไม่เปลี่ยน
|
|
- ไม่มี Regression ต่อหน้า Employee หรือหน้า HRD อื่น
|
|
- Type Check ผ่าน
|
|
- Lint ผ่าน
|
|
- Test ผ่าน
|
|
- Build ผ่าน
|
|
|
|
---
|
|
|
|
# รูปแบบรายงานผลหลังแก้ไข
|
|
|
|
หลังดำเนินการเสร็จ ให้รายงานตามหัวข้อต่อไปนี้
|
|
|
|
## 1. สาเหตุของปัญหา
|
|
|
|
ระบุสาเหตุจริง เช่น:
|
|
|
|
```text
|
|
Mutation สำเร็จ แต่ไม่มีการ invalidate list query
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```text
|
|
invalidate query key ไม่ตรงกับ key ของหน้ารายการ
|
|
```
|
|
|
|
หรือ:
|
|
|
|
```text
|
|
router.refresh ถูกเรียกก่อน server action เสร็จ
|
|
```
|
|
|
|
ห้ามสรุปจากการคาดเดา
|
|
|
|
## 2. Data Fetching Pattern ที่ระบบใช้
|
|
|
|
ระบุว่าใช้:
|
|
|
|
```text
|
|
TanStack Query / SWR / Server Component / Server Action
|
|
```
|
|
|
|
และอธิบาย Source of Truth ของตาราง
|
|
|
|
## 3. ไฟล์ที่แก้ไข
|
|
|
|
ระบุ Path และหน้าที่ของแต่ละไฟล์
|
|
|
|
## 4. Query Key หรือ Cache Key
|
|
|
|
ระบุ Key ก่อนและหลังแก้ไข เช่น:
|
|
|
|
```ts
|
|
trainingRecordKeys.lists();
|
|
```
|
|
|
|
## 5. วิธีอัปเดตข้อมูลหลัง Mutation
|
|
|
|
ระบุว่าใช้:
|
|
|
|
```text
|
|
invalidateQueries
|
|
mutate
|
|
revalidatePath
|
|
revalidateTag
|
|
router.refresh
|
|
setQueryData
|
|
```
|
|
|
|
พร้อมเหตุผล
|
|
|
|
## 6. ผลต่อ Filter และ Pagination
|
|
|
|
อธิบายว่า Draft หายอย่างไร Count อัปเดตอย่างไร และ Page ถูกจัดการอย่างไร
|
|
|
|
## 7. Error Handling
|
|
|
|
อธิบายพฤติกรรมเมื่อ API ล้มเหลว
|
|
|
|
## 8. ผลการทดสอบ
|
|
|
|
รายงาน:
|
|
|
|
- Manual Test
|
|
- Automated Test
|
|
- Type Check
|
|
- Lint
|
|
- Build
|
|
|
|
## 9. Regression Check
|
|
|
|
ยืนยันว่า:
|
|
|
|
- Search ยังทำงาน
|
|
- Filter ยังทำงาน
|
|
- Sorting ยังทำงาน
|
|
- Pagination ยังทำงาน
|
|
- Permission ไม่เปลี่ยน
|
|
- Workflow ไม่เปลี่ยน
|
|
- หน้าอื่นที่ใช้ Query เดียวกันไม่เสีย
|
|
|
|
## 10. ประเด็นคงค้าง
|
|
|
|
หากยังมีข้อจำกัด ให้ระบุตรงไปตรงมา ห้ามรายงานว่างานสมบูรณ์หากยังต้อง Refresh เองในบางกรณี
|
|
|
|
---
|
|
|
|
# ข้อกำหนดสุดท้าย
|
|
|
|
- ตรวจสอบสาเหตุจากโค้ดจริงก่อนแก้ไข
|
|
- แก้ตาม Data Fetching Pattern เดิม
|
|
- ใช้ Query Key หรือ Cache Key กลาง หากมี
|
|
- ห้ามแก้ด้วย `window.location.reload()`
|
|
- ห้ามบังคับ Refresh Browser
|
|
- ห้ามใช้ `setTimeout()` เพื่อรอข้อมูล
|
|
- ห้ามแก้ด้วยการเปลี่ยนสถานะเฉพาะใน DOM
|
|
- ห้ามแสดง Success Toast ก่อน Backend สำเร็จ
|
|
- ห้าม Invalidate Cache ทั้งระบบโดยไม่จำเป็น
|
|
- ห้ามเปลี่ยน Permission หรือ Workflow
|
|
- ต้องยืนยันผลจากทั้ง Network, API Response และ UI
|
|
- ต้องทดสอบทั้งหน้ารวมสถานะและหน้าที่ Filter เฉพาะ Draft
|