1034 lines
35 KiB
Markdown
1034 lines
35 KiB
Markdown
# Prompt: Standardize Auto Refresh After CRUD Operations
|
|
|
|
## บทบาทของคุณ
|
|
|
|
คุณคือ Senior Full-Stack Engineer และ Software Architect ที่รับผิดชอบตรวจสอบและปรับปรุงระบบ Training Management System ซึ่งพัฒนาด้วย Next.js, React, TypeScript และ Data Table
|
|
|
|
ให้ตรวจสอบโครงสร้างเดิมของระบบก่อนดำเนินการแก้ไข และปรับปรุงพฤติกรรมหลังการทำรายการต่าง ๆ ให้ข้อมูลบนหน้าจออัปเดตโดยอัตโนมัติ โดยไม่ต้องให้ผู้ใช้กด Refresh Browser เอง
|
|
|
|
การแก้ไขต้องยึดโครงสร้างและเทคโนโลยีที่ระบบใช้อยู่จริง เช่น:
|
|
|
|
- SWR
|
|
- TanStack Query
|
|
- Next.js Server Components
|
|
- Next.js Server Actions
|
|
- Client-side API Fetching
|
|
- `router.refresh()`
|
|
- Local State
|
|
- Data Table
|
|
- Pagination
|
|
- Search
|
|
- Filter
|
|
- Sorting
|
|
|
|
ห้ามเปลี่ยน Library หรือ Architecture หลักโดยไม่จำเป็น
|
|
|
|
---
|
|
|
|
# 1. วัตถุประสงค์
|
|
|
|
ปรับปรุงระบบให้หลังจากผู้ใช้ดำเนินการสำเร็จ เช่น เพิ่ม แก้ไข ลบ อนุมัติ ปฏิเสธ เผยแพร่ หรือ Import ข้อมูล ระบบต้องอัปเดตข้อมูลบนหน้าจอทันทีโดยอัตโนมัติ
|
|
|
|
ผู้ใช้ต้องไม่ต้อง:
|
|
|
|
- กด Refresh Browser
|
|
- ออกจากหน้าแล้วกลับเข้ามาใหม่
|
|
- เปลี่ยน Tab ไปมา
|
|
- กดค้นหาใหม่
|
|
- เปลี่ยน Pagination เพื่อบังคับโหลดข้อมูล
|
|
|
|
ระบบต้องแสดงข้อมูลล่าสุดที่ตรงกับฐานข้อมูลหลัง Action สำเร็จ
|
|
|
|
---
|
|
|
|
# 2. ขอบเขตการตรวจสอบ
|
|
|
|
ตรวจสอบทุกหน้าที่มีการแสดงรายการข้อมูล และมีการทำ Action ที่ส่งผลต่อข้อมูลในรายการ
|
|
|
|
อย่างน้อยให้ครอบคลุม Module ต่อไปนี้ หากมีอยู่ในระบบ:
|
|
|
|
- Employees
|
|
- Users
|
|
- Training Records
|
|
- Pending Training Reviews
|
|
- Online Lessons
|
|
- Announcements
|
|
- Policies
|
|
- Course Master
|
|
- Permission Templates
|
|
- Roles and Permissions
|
|
- Approval Workflow
|
|
- Notifications
|
|
- Import History
|
|
- Audit Logs
|
|
- Organization Management
|
|
- Departments
|
|
- Positions
|
|
- Companies
|
|
- Reports
|
|
- Settings
|
|
|
|
ให้ค้นหาเพิ่มเติมจาก Codebase ว่ามี Module อื่นที่มี CRUD หรือ Status Action หรือไม่ และรวมไว้ในขอบเขตด้วย
|
|
|
|
---
|
|
|
|
# 3. Action ที่ต้องรองรับ Auto Refresh
|
|
|
|
หลัง Action ต่อไปนี้สำเร็จ ระบบต้อง Refresh หรือ Revalidate ข้อมูลที่เกี่ยวข้องโดยอัตโนมัติ
|
|
|
|
## CRUD Actions
|
|
|
|
- Create
|
|
- Update
|
|
- Delete
|
|
- Soft Delete
|
|
- Restore
|
|
- Archive
|
|
- Unarchive
|
|
|
|
## Workflow Actions
|
|
|
|
- Submit for Review
|
|
- Approve
|
|
- Reject
|
|
- Return for Revision
|
|
- Cancel
|
|
- Publish
|
|
- Unpublish
|
|
- Schedule Publish
|
|
- Pin
|
|
- Unpin
|
|
- Activate
|
|
- Deactivate
|
|
|
|
## Bulk Actions
|
|
|
|
- Bulk Delete
|
|
- Bulk Approve
|
|
- Bulk Reject
|
|
- Bulk Activate
|
|
- Bulk Deactivate
|
|
- Bulk Import
|
|
- Bulk Update
|
|
|
|
## Import Actions
|
|
|
|
- Import Excel
|
|
- Import CSV
|
|
- Import Training Records
|
|
- Import Employee Data
|
|
- Import Course Master
|
|
|
|
หากพบ Action อื่นที่เปลี่ยนแปลงข้อมูล ให้ใช้มาตรฐานเดียวกัน
|
|
|
|
---
|
|
|
|
# 4. ขั้นตอนการตรวจสอบก่อนแก้ไข
|
|
|
|
ก่อนแก้ไข Code ให้ตรวจสอบและระบุว่าแต่ละหน้าดึงข้อมูลด้วยวิธีใด
|
|
|
|
ตัวอย่าง:
|
|
|
|
- SWR
|
|
- TanStack Query
|
|
- Server Component
|
|
- Server Action
|
|
- Client Component Fetch
|
|
- Custom Hook
|
|
- Local State
|
|
- Context
|
|
- Zustand
|
|
- Redux
|
|
- URL Search Params
|
|
|
|
ให้ตรวจสอบว่า:
|
|
|
|
1. Query Key หรือ Cache Key ถูกสร้างที่ใด
|
|
2. Filter ถูกส่งเข้า Query อย่างไร
|
|
3. Pagination ใช้ `page`, `limit`, `offset` หรือ `cursor`
|
|
4. Search ใช้ URL Search Params หรือ Local State
|
|
5. Sort ถูกจัดเก็บไว้ที่ใด
|
|
6. Action Handler อยู่ใน Component, Hook, Service หรือ Server Action
|
|
7. หลัง Action สำเร็จมีการเรียก Refresh หรือ Revalidation อยู่แล้วหรือไม่
|
|
8. มีการ Refresh ผิด Key หรือ Refresh ไม่ครบทุกข้อมูลที่เกี่ยวข้องหรือไม่
|
|
9. มีการใช้ `window.location.reload()` หรือไม่
|
|
10. มีข้อมูลสรุป เช่น Count, Badge, KPI หรือ Dashboard ที่ต้อง Refresh พร้อมกันหรือไม่
|
|
|
|
ห้ามเดารูปแบบการ Fetch ให้ตรวจสอบ Code จริงก่อนแก้ไข
|
|
|
|
---
|
|
|
|
# 5. มาตรฐานการ Refresh ตามเทคโนโลยี
|
|
|
|
เลือกวิธี Refresh ให้ตรงกับ Architecture ของแต่ละหน้า
|
|
|
|
## 5.1 กรณีใช้ SWR
|
|
|
|
หลัง Action สำเร็จให้ใช้ `mutate()` หรือ Global Mutate กับ Key ที่ถูกต้อง
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
const { data, mutate, isLoading } = useSWR(queryKey, fetcher);
|
|
|
|
const handleDelete = async (id: string) => {
|
|
try {
|
|
await deleteRecord(id);
|
|
|
|
await mutate();
|
|
|
|
toast.success("ลบข้อมูลสำเร็จ");
|
|
} catch (error) {
|
|
toast.error(getErrorMessage(error));
|
|
}
|
|
};
|
|
```
|
|
|
|
กรณีใช้ Dynamic Key ให้ตรวจสอบว่า Key ที่ Mutate ตรงกับ Key ที่ใช้ Fetch จริง
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
await mutate(getTrainingRecordsKey(filters));
|
|
```
|
|
|
|
หาก Action ส่งผลต่อหลาย Key ให้ Revalidate ทุก Key ที่เกี่ยวข้อง
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
await Promise.all([
|
|
mutate(getTrainingRecordsKey(filters)),
|
|
mutate(getPendingReviewCountKey()),
|
|
mutate(getDashboardSummaryKey()),
|
|
]);
|
|
```
|
|
|
|
ห้ามสร้าง Cache Key ซ้ำหลายรูปแบบโดยไม่จำเป็น
|
|
|
|
---
|
|
|
|
## 5.2 กรณีใช้ TanStack Query
|
|
|
|
หลัง Action สำเร็จให้ใช้ `invalidateQueries()` หรือ `setQueryData()` ตามความเหมาะสม
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
await deleteRecord(id);
|
|
|
|
await queryClient.invalidateQueries({
|
|
queryKey: ["training-records"],
|
|
});
|
|
|
|
toast.success("ลบข้อมูลสำเร็จ");
|
|
```
|
|
|
|
หาก Query Key มี Filter:
|
|
|
|
```tsx
|
|
await queryClient.invalidateQueries({
|
|
queryKey: ["training-records", filters],
|
|
});
|
|
```
|
|
|
|
หาก Action ส่งผลต่อหลายรายการ:
|
|
|
|
```tsx
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["training-records"],
|
|
}),
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["dashboard-summary"],
|
|
}),
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["pending-review-count"],
|
|
}),
|
|
]);
|
|
```
|
|
|
|
หลีกเลี่ยงการ Invalidate Cache ทั้งระบบโดยไม่จำเป็น
|
|
|
|
---
|
|
|
|
## 5.3 กรณีใช้ Next.js Server Component หรือ Server Action
|
|
|
|
ให้ใช้วิธีที่เหมาะสม เช่น:
|
|
|
|
- `router.refresh()`
|
|
- `revalidatePath()`
|
|
- `revalidateTag()`
|
|
|
|
ตัวอย่างฝั่ง Client:
|
|
|
|
```tsx
|
|
const router = useRouter();
|
|
|
|
await deleteRecord(id);
|
|
|
|
router.refresh();
|
|
|
|
toast.success("ลบข้อมูลสำเร็จ");
|
|
```
|
|
|
|
ตัวอย่าง Server Action:
|
|
|
|
```tsx
|
|
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
|
|
export async function deleteTrainingRecord(id: string) {
|
|
await trainingRecordService.delete(id);
|
|
|
|
revalidatePath("/dashboard/training-records");
|
|
}
|
|
```
|
|
|
|
หากมีหลายหน้าที่ใช้ข้อมูลเดียวกัน ให้พิจารณาใช้ `revalidateTag()` แทนการระบุ Path ซ้ำหลายจุด
|
|
|
|
ห้ามเรียก `router.refresh()` โดยไม่จำเป็นในหน้าที่ใช้ SWR หรือ TanStack Query หากการ Refresh ไม่ได้ทำให้ Cache ของ Client ถูกอัปเดต
|
|
|
|
---
|
|
|
|
## 5.4 กรณีใช้ Local State
|
|
|
|
หากรายการถูกเก็บอยู่ใน Local State และไม่มี Data Fetching Library ให้ปรับ State หลัง Action สำเร็จ
|
|
|
|
ตัวอย่าง Delete:
|
|
|
|
```tsx
|
|
setItems((currentItems) =>
|
|
currentItems.filter((item) => item.id !== deletedId),
|
|
);
|
|
```
|
|
|
|
ตัวอย่าง Update:
|
|
|
|
```tsx
|
|
setItems((currentItems) =>
|
|
currentItems.map((item) => (item.id === updatedItem.id ? updatedItem : item)),
|
|
);
|
|
```
|
|
|
|
อย่างไรก็ตาม หากฐานข้อมูลเป็น Source of Truth ให้พิจารณา Fetch ข้อมูลใหม่หลัง Action สำเร็จเพื่อป้องกันข้อมูลไม่ตรงกับ Server
|
|
|
|
---
|
|
|
|
# 6. ลำดับการทำงานมาตรฐาน
|
|
|
|
กำหนดลำดับหลัง Action สำเร็จให้เหมือนกันทั้งระบบ
|
|
|
|
```text
|
|
User confirms action
|
|
↓
|
|
Set loading state
|
|
↓
|
|
Call API / Server Action
|
|
↓
|
|
Server returns success
|
|
↓
|
|
Refresh / Revalidate affected data
|
|
↓
|
|
Adjust pagination if necessary
|
|
↓
|
|
Close Dialog / Drawer / Sheet
|
|
↓
|
|
Reset form or selected rows
|
|
↓
|
|
Show success toast
|
|
↓
|
|
Remain on the current screen
|
|
```
|
|
|
|
กรณี Create หรือ Update ที่ต้องกลับไปหน้า List:
|
|
|
|
```text
|
|
Submit form
|
|
↓
|
|
Server returns success
|
|
↓
|
|
Revalidate list data
|
|
↓
|
|
Navigate back to list
|
|
↓
|
|
List displays latest data
|
|
↓
|
|
Show success toast
|
|
```
|
|
|
|
ต้องป้องกันการกด Action ซ้ำระหว่างกำลังดำเนินการ
|
|
|
|
---
|
|
|
|
# 7. Pagination Handling
|
|
|
|
ต้องรองรับกรณีลบรายการสุดท้ายของหน้าปัจจุบัน
|
|
|
|
ตัวอย่าง:
|
|
|
|
- ผู้ใช้อยู่หน้า 3
|
|
- หน้า 3 มีข้อมูลเพียง 1 รายการ
|
|
- ผู้ใช้ลบรายการนั้น
|
|
- หลังลบ หน้า 3 ไม่มีข้อมูลเหลือ
|
|
|
|
ระบบต้องกลับไปหน้าก่อนหน้าโดยอัตโนมัติ
|
|
|
|
ตัวอย่าง Logic:
|
|
|
|
```tsx
|
|
const isLastItemOnPage = currentItems.length === 1;
|
|
const shouldGoToPreviousPage = isLastItemOnPage && currentPage > 1;
|
|
|
|
if (shouldGoToPreviousPage) {
|
|
setPage(currentPage - 1);
|
|
} else {
|
|
await refreshData();
|
|
}
|
|
```
|
|
|
|
หาก Pagination ถูกควบคุมผ่าน URL Search Params ให้ Update URL แทน Local State
|
|
|
|
ต้องไม่ปล่อยให้ผู้ใช้เห็นหน้าว่าง ทั้งที่หน้าก่อนหน้ายังมีข้อมูล
|
|
|
|
---
|
|
|
|
# 8. Preserve User Context
|
|
|
|
หลัง Refresh ข้อมูล ต้องรักษาบริบทของผู้ใช้ไว้ให้มากที่สุด
|
|
|
|
ห้าม Reset ค่าเหล่านี้โดยไม่จำเป็น:
|
|
|
|
- Current Page
|
|
- Page Size
|
|
- Search Keyword
|
|
- Filters
|
|
- Status Tab
|
|
- Selected Year
|
|
- Company Filter
|
|
- Department Filter
|
|
- Employee Filter
|
|
- Sort Order
|
|
- Column Visibility
|
|
- Date Range
|
|
|
|
ตัวอย่าง:
|
|
|
|
ก่อนลบ:
|
|
|
|
```text
|
|
Page: 2
|
|
Search: safety
|
|
Status: approved
|
|
Year: 2026
|
|
Sort: created_at desc
|
|
```
|
|
|
|
หลังลบสำเร็จ ระบบต้องยังอยู่ในบริบทเดิม เว้นแต่หน้าปัจจุบันไม่สามารถใช้งานได้ เช่น ลบรายการสุดท้ายของหน้า
|
|
|
|
---
|
|
|
|
# 9. Sorting Standard
|
|
|
|
หลัง Create หรือ Update สำเร็จ ต้องตรวจสอบว่ารายการแสดงตาม Sorting ของระบบจริง
|
|
|
|
หากมาตรฐานของหน้ารายการคือข้อมูลที่สร้างใหม่แสดงก่อน ให้ Query ใช้:
|
|
|
|
```text
|
|
created_at DESC
|
|
```
|
|
|
|
ห้ามใช้ `updated_at DESC` แทนโดยไม่ตั้งใจ เพราะการแก้ไขรายการเก่าอาจทำให้รายการนั้นย้ายขึ้นไปด้านบน ทั้งที่มาตรฐานต้องการเรียงตามวันที่สร้าง
|
|
|
|
ให้ใช้ Sorting ตาม Requirement ของแต่ละหน้า และคง Sort เดิมหลัง Refresh
|
|
|
|
---
|
|
|
|
# 10. Dialog, Drawer และ Form Handling
|
|
|
|
หลัง Action สำเร็จ:
|
|
|
|
- ปิด Dialog, Drawer หรือ Sheet
|
|
- Reset Form เมื่อเหมาะสม
|
|
- Reset Selected File
|
|
- Reset Selected Rows
|
|
- Reset Delete Confirmation Text
|
|
- Clear Temporary State
|
|
- ปลด Loading State
|
|
- ป้องกัน Dialog ปิดก่อน Server ตอบกลับสำเร็จ
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
try {
|
|
setIsSubmitting(true);
|
|
|
|
await updateRecord(values);
|
|
|
|
await mutate();
|
|
|
|
setOpen(false);
|
|
form.reset();
|
|
|
|
toast.success("บันทึกข้อมูลสำเร็จ");
|
|
} catch (error) {
|
|
toast.error(getErrorMessage(error));
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
```
|
|
|
|
กรณี Action ล้มเหลว:
|
|
|
|
- ห้ามปิด Dialog
|
|
- ห้าม Reset Form
|
|
- ห้าม Refresh รายการโดยไม่จำเป็น
|
|
- ต้องแสดง Error ที่เข้าใจได้
|
|
- ต้องคงข้อมูลที่ผู้ใช้กรอกไว้
|
|
|
|
---
|
|
|
|
# 11. Toast และ Feedback
|
|
|
|
ให้ใช้รูปแบบข้อความที่ชัดเจนและสม่ำเสมอ
|
|
|
|
ตัวอย่าง:
|
|
|
|
```text
|
|
เพิ่มข้อมูลสำเร็จ
|
|
บันทึกข้อมูลสำเร็จ
|
|
แก้ไขข้อมูลสำเร็จ
|
|
ลบข้อมูลสำเร็จ
|
|
ส่งตรวจสอบสำเร็จ
|
|
อนุมัติรายการสำเร็จ
|
|
ปฏิเสธรายการสำเร็จ
|
|
เผยแพร่ข้อมูลสำเร็จ
|
|
นำเข้าข้อมูลสำเร็จ
|
|
```
|
|
|
|
ไม่ควรแสดง Success Toast ก่อน Refresh ข้อมูลเสร็จ หากอาจทำให้ผู้ใช้เห็น Toast สำเร็จแต่ข้อมูลบนหน้าจอยังไม่เปลี่ยน
|
|
|
|
ลำดับที่แนะนำ:
|
|
|
|
```text
|
|
API Success
|
|
→ Refresh Data
|
|
→ Close Dialog
|
|
→ Toast Success
|
|
```
|
|
|
|
หาก Refresh ล้มเหลวหลัง Server บันทึกสำเร็จ ให้แสดงข้อความที่สะท้อนสถานะจริง เช่น:
|
|
|
|
```text
|
|
บันทึกข้อมูลสำเร็จ แต่ไม่สามารถโหลดข้อมูลล่าสุดได้ กรุณาลองใหม่อีกครั้ง
|
|
```
|
|
|
|
---
|
|
|
|
# 12. Loading State
|
|
|
|
ระหว่าง Action ต้องมี Loading State ที่ชัดเจน
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
<Button disabled={isPending}>{isPending ? "กำลังบันทึก..." : "บันทึก"}</Button>
|
|
```
|
|
|
|
สำหรับ Delete:
|
|
|
|
```tsx
|
|
<Button variant="destructive" disabled={isDeleting}>
|
|
{isDeleting ? "กำลังลบ..." : "ยืนยันการลบ"}
|
|
</Button>
|
|
```
|
|
|
|
ห้ามให้ผู้ใช้กด Action ซ้ำระหว่าง Request กำลังทำงาน
|
|
|
|
หาก Refresh Data ใช้เวลานาน ให้คง Loading State จนกว่าจะ Refresh เสร็จ
|
|
|
|
---
|
|
|
|
# 13. Optimistic Update
|
|
|
|
สามารถใช้ Optimistic Update ได้เฉพาะเมื่อมีความเหมาะสมและมี Rollback ที่ถูกต้อง
|
|
|
|
ตัวอย่างกรณี Delete:
|
|
|
|
```tsx
|
|
const previousData = data;
|
|
|
|
mutate(
|
|
{
|
|
...data,
|
|
items: data.items.filter((item) => item.id !== id),
|
|
},
|
|
false,
|
|
);
|
|
|
|
try {
|
|
await deleteRecord(id);
|
|
await mutate();
|
|
} catch (error) {
|
|
mutate(previousData, false);
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
ห้ามใช้ Optimistic Update หาก:
|
|
|
|
- Action มี Workflow ซับซ้อน
|
|
- Server อาจเปลี่ยนข้อมูลเพิ่มเติม
|
|
- มี Permission Validation
|
|
- มี Approval State
|
|
- มีข้อมูล Summary หลายจุด
|
|
- มีความเสี่ยงที่ Client State จะไม่ตรงกับ Server
|
|
|
|
กรณีดังกล่าวให้ Refresh จาก Server หลัง Action สำเร็จ
|
|
|
|
---
|
|
|
|
# 14. ห้ามใช้ Full Page Reload
|
|
|
|
ห้ามใช้วิธีต่อไปนี้เป็นแนวทางหลัก:
|
|
|
|
```tsx
|
|
window.location.reload();
|
|
```
|
|
|
|
```tsx
|
|
location.reload();
|
|
```
|
|
|
|
```tsx
|
|
window.location.href = window.location.href;
|
|
```
|
|
|
|
ห้าม Navigate ออกจากหน้าแล้วกลับเข้ามาใหม่เพียงเพื่อให้ข้อมูล Refresh
|
|
|
|
อนุญาตเฉพาะกรณีมีข้อจำกัดทางเทคนิคที่พิสูจน์ได้ และต้องระบุเหตุผลใน Implementation Report
|
|
|
|
---
|
|
|
|
# 15. Related Data Revalidation
|
|
|
|
ให้ตรวจสอบว่า Action หนึ่งส่งผลต่อข้อมูลส่วนอื่นหรือไม่
|
|
|
|
ตัวอย่าง การอนุมัติ Training Record อาจส่งผลต่อ:
|
|
|
|
- Training Records List
|
|
- Pending Review List
|
|
- Pending Review Count
|
|
- Employee Training Hours
|
|
- Employee Dashboard
|
|
- HRD Dashboard
|
|
- K/S/A Summary
|
|
- Notification List
|
|
- Audit Log
|
|
|
|
ดังนั้นหลัง Approve อาจต้อง Refresh มากกว่าหนึ่ง Query
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
await Promise.all([
|
|
mutate(getPendingTrainingRecordsKey(filters)),
|
|
mutate(getApprovedTrainingRecordsKey()),
|
|
mutate(getEmployeeTrainingSummaryKey(employeeId, year)),
|
|
mutate(getHRDDashboardKey(year)),
|
|
mutate(getNotificationCountKey()),
|
|
]);
|
|
```
|
|
|
|
ให้ทำ Dependency Mapping สำหรับ Action สำคัญก่อนแก้ไข
|
|
|
|
---
|
|
|
|
# 16. Cache Key Standardization
|
|
|
|
ตรวจสอบและปรับ Cache Key ให้มีมาตรฐาน
|
|
|
|
ตัวอย่าง SWR Key:
|
|
|
|
```tsx
|
|
export const trainingRecordsKeys = {
|
|
all: "/api/training-records",
|
|
list: (filters: TrainingRecordFilters) => ["/api/training-records", filters],
|
|
detail: (id: string) => ["/api/training-records", id],
|
|
summary: (employeeId: string, year: number) => [
|
|
"/api/training-records/summary",
|
|
employeeId,
|
|
year,
|
|
],
|
|
};
|
|
```
|
|
|
|
ตัวอย่าง TanStack Query Key:
|
|
|
|
```tsx
|
|
export const trainingRecordKeys = {
|
|
all: ["training-records"] as const,
|
|
lists: () => [...trainingRecordKeys.all, "list"] as const,
|
|
list: (filters: TrainingRecordFilters) =>
|
|
[...trainingRecordKeys.lists(), filters] as const,
|
|
details: () => [...trainingRecordKeys.all, "detail"] as const,
|
|
detail: (id: string) => [...trainingRecordKeys.details(), id] as const,
|
|
};
|
|
```
|
|
|
|
หลีกเลี่ยงการเขียน String Key กระจัดกระจายในหลายไฟล์
|
|
|
|
---
|
|
|
|
# 17. Error Handling
|
|
|
|
ทุก Action ต้องมี Error Handling ที่สม่ำเสมอ
|
|
|
|
```tsx
|
|
try {
|
|
await action();
|
|
await refreshData();
|
|
|
|
toast.success("ดำเนินการสำเร็จ");
|
|
} catch (error) {
|
|
toast.error(getErrorMessage(error));
|
|
}
|
|
```
|
|
|
|
ต้องแยกกรณี:
|
|
|
|
- API Action ล้มเหลว
|
|
- Permission Denied
|
|
- Validation Error
|
|
- Record Not Found
|
|
- Conflict
|
|
- Network Error
|
|
- Refresh Data ล้มเหลวหลัง Action สำเร็จ
|
|
|
|
ห้ามกลืน Error โดยไม่แสดงผล
|
|
|
|
ห้ามแสดง Success Toast หาก API Action ล้มเหลว
|
|
|
|
---
|
|
|
|
# 18. Permission และ Security
|
|
|
|
การเพิ่ม Auto Refresh ต้องไม่ลดทอน Security
|
|
|
|
ต้องตรวจสอบว่า:
|
|
|
|
- Permission Validation ยังอยู่ที่ Server
|
|
- ผู้ใช้ไม่สามารถเห็นข้อมูลที่ไม่มีสิทธิ์หลัง Refresh
|
|
- Cache Key ต้องแยกตาม Scope ที่จำเป็น
|
|
- ข้อมูลระหว่าง Company หรือ Department ต้องไม่ปะปนกัน
|
|
- ห้ามพึ่ง Client-side Filtering เพื่อควบคุมสิทธิ์
|
|
- Server ต้องเป็นผู้คืนข้อมูลตาม Permission ของผู้ใช้
|
|
|
|
การ Refresh เป็นเพียงการโหลดข้อมูลใหม่ ไม่ใช่กลไก Authorization
|
|
|
|
---
|
|
|
|
# 19. Code Quality
|
|
|
|
การแก้ไขต้อง:
|
|
|
|
- ใช้ TypeScript Type ที่ชัดเจน
|
|
- หลีกเลี่ยง `any`
|
|
- ไม่ Duplicate Logic
|
|
- สร้าง Helper หรือ Custom Hook หาก Pattern ถูกใช้ซ้ำ
|
|
- ใช้ชื่อ Function ที่สื่อความหมาย
|
|
- ไม่เพิ่ม Dependency ใหม่โดยไม่จำเป็น
|
|
- รักษา Existing Coding Style
|
|
- รักษา Existing Folder Structure
|
|
- ไม่แก้ไขส่วนที่ไม่เกี่ยวข้อง
|
|
- ไม่เปลี่ยน API Contract โดยไม่จำเป็น
|
|
|
|
หากระบบมี Pattern กลางอยู่แล้ว ให้ปรับปรุง Pattern เดิมแทนสร้าง Pattern ใหม่ซ้ำซ้อน
|
|
|
|
---
|
|
|
|
# 20. Shared Helper หรือ Custom Hook
|
|
|
|
หากพบ Logic ซ้ำหลายหน้า ให้พิจารณาสร้าง Shared Helper
|
|
|
|
ตัวอย่าง:
|
|
|
|
```tsx
|
|
type RefreshOptions = {
|
|
refreshList?: () => Promise<unknown>;
|
|
refreshSummary?: () => Promise<unknown>;
|
|
closeDialog?: () => void;
|
|
resetForm?: () => void;
|
|
successMessage: string;
|
|
};
|
|
|
|
export async function handleSuccessfulMutation({
|
|
refreshList,
|
|
refreshSummary,
|
|
closeDialog,
|
|
resetForm,
|
|
successMessage,
|
|
}: RefreshOptions) {
|
|
await Promise.all([refreshList?.(), refreshSummary?.()]);
|
|
|
|
closeDialog?.();
|
|
resetForm?.();
|
|
|
|
toast.success(successMessage);
|
|
}
|
|
```
|
|
|
|
อย่างไรก็ตาม ห้ามสร้าง Abstraction ที่ซับซ้อนเกินความจำเป็น
|
|
|
|
ให้เลือกเฉพาะกรณีที่ช่วยลด Code ซ้ำและทำให้ระบบดูแลต่อได้ง่ายขึ้นจริง
|
|
|
|
---
|
|
|
|
# 21. Acceptance Criteria
|
|
|
|
งานจะถือว่าเสร็จเมื่อผ่านเงื่อนไขทั้งหมดต่อไปนี้
|
|
|
|
## Functional
|
|
|
|
- หลัง Create สำเร็จ รายการใหม่แสดงบน List โดยอัตโนมัติ
|
|
- หลัง Update สำเร็จ ข้อมูลที่แก้ไขแสดงทันที
|
|
- หลัง Delete สำเร็จ รายการหายจาก List ทันที
|
|
- หลัง Approve รายการย้ายออกจาก Pending และไปอยู่ Approved
|
|
- หลัง Reject สถานะใหม่แสดงทันที
|
|
- หลัง Publish สถานะ Published แสดงทันที
|
|
- หลัง Import รายการที่นำเข้าแสดงโดยไม่ต้อง Refresh Browser
|
|
- Count, Badge และ Summary ที่เกี่ยวข้องอัปเดตพร้อมกัน
|
|
- ไม่มีข้อมูลเก่าค้างบนหน้าจอหลัง Action สำเร็จ
|
|
|
|
## Pagination
|
|
|
|
- การลบรายการสุดท้ายของหน้าไม่ทำให้ค้างอยู่หน้าว่าง
|
|
- ระบบกลับไปหน้าก่อนหน้าเมื่อจำเป็น
|
|
- Page Size ไม่ถูก Reset
|
|
- Search และ Filter เดิมยังอยู่
|
|
|
|
## UX
|
|
|
|
- Dialog ปิดหลัง Action และ Refresh สำเร็จ
|
|
- Form ถูก Reset เมื่อเหมาะสม
|
|
- มี Loading State
|
|
- ปุ่มไม่สามารถกดซ้ำได้
|
|
- Success Toast แสดงหลังข้อมูลอัปเดตแล้ว
|
|
- Error ไม่ทำให้ข้อมูลใน Form หาย
|
|
|
|
## Technical
|
|
|
|
- ไม่มีการใช้ `window.location.reload()` เป็นแนวทางหลัก
|
|
- Cache Key ถูกต้อง
|
|
- ไม่มีการ Refresh Query ที่ไม่เกี่ยวข้องจำนวนมากเกินไป
|
|
- ไม่เกิด Infinite Request Loop
|
|
- ไม่เกิด Duplicate Fetch ที่ผิดปกติ
|
|
- ไม่เกิด React State Update หลัง Component Unmount
|
|
- TypeScript ผ่าน
|
|
- ESLint ผ่าน
|
|
- Build ผ่าน
|
|
- Existing Tests ผ่าน
|
|
|
|
---
|
|
|
|
# 22. Test Cases ที่ต้องดำเนินการ
|
|
|
|
ให้ทดสอบอย่างน้อยตามรายการต่อไปนี้
|
|
|
|
## Create
|
|
|
|
1. เปิดหน้ารายการ
|
|
2. เพิ่มข้อมูลใหม่
|
|
3. บันทึกสำเร็จ
|
|
4. ตรวจสอบว่ารายการใหม่ปรากฏตาม Sorting
|
|
5. ตรวจสอบว่าไม่ต้อง Refresh Browser
|
|
6. ตรวจสอบว่า Filter และ Search เดิมยังอยู่
|
|
|
|
## Update
|
|
|
|
1. แก้ไขรายการ
|
|
2. บันทึกสำเร็จ
|
|
3. ตรวจสอบว่าข้อมูลบน Table เปลี่ยนทันที
|
|
4. เปิด Detail ตรวจสอบข้อมูลล่าสุด
|
|
5. ตรวจสอบว่าไม่มีข้อมูลเก่าจาก Cache
|
|
|
|
## Delete
|
|
|
|
1. ลบรายการที่อยู่กลางหน้า
|
|
2. ตรวจสอบว่ารายการหายทันที
|
|
3. ลบรายการสุดท้ายของหน้า
|
|
4. ตรวจสอบว่าระบบกลับหน้าก่อนหน้าเมื่อจำเป็น
|
|
5. ตรวจสอบ Total Count
|
|
|
|
## Status Action
|
|
|
|
1. Submit รายการ Draft
|
|
2. ตรวจสอบว่ารายการหายจาก Draft
|
|
3. ตรวจสอบว่ารายการปรากฏใน Pending
|
|
4. Approve รายการ
|
|
5. ตรวจสอบว่า Pending Count ลดลง
|
|
6. ตรวจสอบว่า Approved Count เพิ่มขึ้น
|
|
7. ตรวจสอบ Dashboard Summary
|
|
|
|
## Error
|
|
|
|
1. จำลอง API Error
|
|
2. ตรวจสอบว่า Dialog ไม่ปิด
|
|
3. ตรวจสอบว่า Form ไม่ถูก Reset
|
|
4. ตรวจสอบว่า Success Toast ไม่แสดง
|
|
5. ตรวจสอบว่า Error Message แสดงถูกต้อง
|
|
|
|
## Concurrent Data
|
|
|
|
1. เปิดข้อมูลเดียวกันสอง Tab
|
|
2. แก้ไขข้อมูลจาก Tab แรก
|
|
3. ทำ Action จาก Tab ที่สอง
|
|
4. ตรวจสอบ Conflict Handling
|
|
5. ตรวจสอบว่าข้อมูลล่าสุดถูกโหลดหลัง Action
|
|
|
|
---
|
|
|
|
# 23. รูปแบบการดำเนินงาน
|
|
|
|
ให้ดำเนินการตามลำดับดังนี้
|
|
|
|
## Phase 1: Audit
|
|
|
|
- ค้นหาหน้าทั้งหมดที่มี CRUD และ Workflow Actions
|
|
- ระบุ Data Fetching Method ของแต่ละหน้า
|
|
- ระบุ Action ที่ยังไม่มี Auto Refresh
|
|
- ระบุ Query Key หรือ Cache Key ที่เกี่ยวข้อง
|
|
- ระบุผลกระทบต่อ Related Data
|
|
- ระบุจุดที่ใช้ `window.location.reload()`
|
|
- จัดทำรายการไฟล์ที่จะต้องแก้ไข
|
|
|
|
## Phase 2: Design
|
|
|
|
- กำหนด Refresh Strategy ของแต่ละหน้า
|
|
- กำหนด Cache Key Standard
|
|
- กำหนด Pagination Handling
|
|
- กำหนด Dependency Mapping
|
|
- กำหนด Shared Helper หากจำเป็น
|
|
|
|
## Phase 3: Implementation
|
|
|
|
- แก้ไข Action Handler
|
|
- เพิ่ม Refresh หรือ Revalidation
|
|
- ปรับ Loading State
|
|
- ปรับ Dialog และ Form Reset
|
|
- ปรับ Pagination หลัง Delete
|
|
- ปรับ Related Data Refresh
|
|
- ลบ Full Page Reload ที่ไม่จำเป็น
|
|
|
|
## Phase 4: Verification
|
|
|
|
- Run Type Check
|
|
- Run Lint
|
|
- Run Tests
|
|
- Run Build
|
|
- ทดสอบ Manual Test Cases
|
|
- ตรวจสอบ Regression
|
|
- ตรวจสอบ Network Request
|
|
- ตรวจสอบ Infinite Loop
|
|
|
|
---
|
|
|
|
# 24. Implementation Report
|
|
|
|
หลังดำเนินการเสร็จ ให้จัดทำรายงานสรุปใน Markdown โดยมีหัวข้ออย่างน้อยดังนี้
|
|
|
|
```md
|
|
# Auto Refresh Implementation Report
|
|
|
|
## 1. Executive Summary
|
|
|
|
## 2. Modules Audited
|
|
|
|
## 3. Problems Found
|
|
|
|
## 4. Root Causes
|
|
|
|
## 5. Files Changed
|
|
|
|
## 6. Refresh Strategy by Module
|
|
|
|
## 7. Cache Keys Updated
|
|
|
|
## 8. Pagination Handling
|
|
|
|
## 9. Related Data Revalidation
|
|
|
|
## 10. Full Page Reloads Removed
|
|
|
|
## 11. Test Results
|
|
|
|
## 12. Remaining Risks
|
|
|
|
## 13. Recommendations
|
|
```
|
|
|
|
ในหัวข้อ Files Changed ให้ระบุ:
|
|
|
|
- Path ของไฟล์
|
|
- สิ่งที่แก้ไข
|
|
- เหตุผลที่แก้ไข
|
|
- ผลกระทบ
|
|
- วิธีทดสอบ
|
|
|
|
---
|
|
|
|
# 25. ข้อจำกัดสำคัญ
|
|
|
|
- ห้ามแก้ไข Business Logic โดยไม่เกี่ยวข้องกับปัญหา
|
|
- ห้ามเปลี่ยน Workflow Approval
|
|
- ห้ามเปลี่ยน Permission Model
|
|
- ห้ามเปลี่ยน Database Schema หากไม่จำเป็น
|
|
- ห้ามเพิ่ม Library ใหม่โดยไม่จำเป็น
|
|
- ห้ามเปลี่ยน UI Layout โดยไม่เกี่ยวข้อง
|
|
- ห้ามลบ Feature เดิม
|
|
- ห้ามใช้ Full Page Reload เป็นวิธีแก้หลัก
|
|
- ห้าม Refresh Cache ทั้งระบบโดยไม่จำเป็น
|
|
- ห้ามปิด Dialog ก่อน Action สำเร็จ
|
|
- ห้ามแสดง Success Toast ก่อน Server ตอบกลับสำเร็จ
|
|
- ห้าม Reset Search, Filter หรือ Pagination โดยไม่จำเป็น
|
|
|
|
หากพบปัญหาอื่นนอกขอบเขต ให้บันทึกไว้ใน Implementation Report แต่ไม่ต้องแก้ไข เว้นแต่เป็น Blocking Issue ที่ทำให้งานนี้ไม่สามารถทำงานได้
|
|
|
|
---
|
|
|
|
# 26. Definition of Done
|
|
|
|
งานนี้ถือว่าเสร็จสมบูรณ์เมื่อ:
|
|
|
|
1. ทุกหน้าที่มี CRUD หรือ Workflow Action ได้รับการตรวจสอบ
|
|
2. ทุก Action สำคัญมี Auto Refresh
|
|
3. ข้อมูลบนหน้าจอตรงกับ Server หลัง Action
|
|
4. Related Count และ Summary อัปเดตถูกต้อง
|
|
5. Search, Filter, Sort และ Pagination ยังคงเดิม
|
|
6. Delete รายการสุดท้ายของหน้าถูกจัดการถูกต้อง
|
|
7. ไม่มี Full Page Reload ที่ไม่จำเป็น
|
|
8. ไม่มี Infinite Fetch Loop
|
|
9. TypeScript, Lint และ Build ผ่าน
|
|
10. มี Implementation Report ฉบับสมบูรณ์
|
|
|
|
---
|
|
|
|
# คำสั่งเริ่มต้น
|
|
|
|
เริ่มจากตรวจสอบ Codebase ปัจจุบันก่อน ห้ามแก้ไขทันทีโดยยังไม่ทราบว่าแต่ละหน้าใช้ Data Fetching และ Cache Strategy แบบใด
|
|
|
|
จากนั้นให้สรุป:
|
|
|
|
1. Module ที่ตรวจพบ
|
|
2. หน้าที่มีปัญหา Auto Refresh
|
|
3. วิธี Fetch ข้อมูลของแต่ละหน้า
|
|
4. Action ที่ยังไม่ Refresh
|
|
5. Cache Key ที่เกี่ยวข้อง
|
|
6. ไฟล์ที่ต้องแก้ไข
|
|
7. แผนการแก้ไข
|
|
|
|
เมื่อสรุปแล้ว ให้ดำเนินการแก้ไขทุกจุดที่อยู่ในขอบเขตจนเสร็จ พร้อมทดสอบและจัดทำ Implementation Report
|
|
|
|
ไม่ต้องหยุดเพื่อถามยืนยันระหว่างการดำเนินงาน เว้นแต่พบข้อขัดแย้งที่ไม่สามารถตัดสินใจจาก Codebase และ Requirement ที่มีอยู่ได้
|