commit
This commit is contained in:
888
plans/online-lesson-submit-review-status-refresh-plan.md
Normal file
888
plans/online-lesson-submit-review-status-refresh-plan.md
Normal file
@@ -0,0 +1,888 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user