# สรุปข้อกำหนด: ระบบแสดง Icon Status บนการ์ดพนักงาน

## ภาพรวม

พัฒนาระบบแสดงไอคอนและป้ายสถานะ (Status Icon & Label) บนการ์ดพนักงานในหน้าเลือกช่างซ่อมบำรุง (AssignMaintenanceSupportDrawer) แทนที่ placeholder "ICON" และ "STATUS" ที่มีอยู่ใน `EmployeeItem` component

## ที่มาของข้อมูลสถานะ

ข้อมูลสถานะมาจาก API (`/call-terminal/employee`) ซึ่ง query จาก DB โดยตรง:

- **is_available** (boolean): `true` = ไม่มีงาน `during work` ที่ active อยู่ (ว่างรับงานได้)
- **is_working** (boolean): `true` = มีงาน `during work` ที่ active และไม่ถูก suspend (กำลังทำงานอยู่)
- **Off Shift**: คำนวณจากกะหมุนเวียน — ดู `ms_shift_pattern_week_detail` ว่าวันนี้พนักงาน (ตาม work_group_id) ทำกะอะไร แล้วเทียบเวลาปัจจุบันกับ `start_ts` / `end_ts` ของกะนั้น

> ⚠️ ปัจจุบัน API ยังไม่มี field `is_off_shift` — ต้องเพิ่ม logic คำนวณจาก `ms_shift_pattern` + `ms_shift_pattern_week_detail` + `ms_shift` (ผ่าน `work_group_id` ของพนักงาน)

### SQL Query ปัจจุบัน (employee.repository.ts)

```sql
-- is_available: ไม่มีงาน during work
NOT EXISTS (
  SELECT 1 FROM mp_request_work_worker mp
  JOIN request_work rw ON rw.id = mp.request_work_id
  WHERE mp.employee_id = me.id
    AND mp.worker_type = 'employee'
    AND mp.is_active = TRUE
    AND rw.current_status = 'during work'
) AS is_available

-- is_working: มีงาน during work ที่ไม่ถูก suspend
EXISTS (
  SELECT 1 FROM mp_request_work_worker mp
  JOIN request_work rw ON rw.id = mp.request_work_id
  WHERE mp.employee_id = me.id
    AND mp.worker_type = 'employee'
    AND mp.is_active = TRUE
    AND rw.current_status = 'during work'
    AND rw.is_suspension = FALSE
) AS is_working

-- shift_start_ts / shift_end_ts: ดึงจาก ms_shift ผ่าน work_shift_id (กะตายตัว)
CAST(ms.start_ts AS TEXT) AS shift_start_ts,
CAST(ms.end_ts AS TEXT) AS shift_end_ts
-- JOIN: LEFT JOIN ms_shift ms ON ms.id = me.work_shift_id
```

### ปัญหาของ SQL ปัจจุบัน

- `shift_start_ts` / `shift_end_ts` ดึงจาก `ms_shift` ผ่าน `work_shift_id` ตรงๆ
- ใช้ได้แค่กะตายตัว (เช่น กลุ่ม C ที่ทำ 08:00-17:00 ตลอด)
- **ไม่รองรับกะหมุน** — เพราะกลุ่ม A อาจทำกะเช้า (06:00-14:30) สัปดาห์นี้ แต่ทำกะบ่าย (14:30-23:00) สัปดาห์หน้า

### Frontend Logic ปัจจุบัน (useInteralAssignMaintenanceDrawer.ts)

- Function `isInShiftRange()` เทียบเวลาปัจจุบันกับ `shift_start_ts`/`shift_end_ts` ที่ได้จาก API
- รองรับกะข้ามวัน (overnight shift) อยู่แล้ว
- **ไม่ต้องแก้ Frontend** ถ้า Backend ส่ง `shift_start_ts`/`shift_end_ts` ที่ถูกต้องมา

### SQL ที่ต้องแก้ (รองรับกะหมุนเวียน)

เปลี่ยนจาก JOIN `ms_shift` ผ่าน `work_shift_id` เป็นคำนวณจาก pattern:

```sql
SELECT
  me.id,
  mct.value AS department_incharge_value,
  me.firstname,
  me.lastname,
  NOT EXISTS (
    SELECT 1
    FROM mp_request_work_worker mp
    JOIN request_work rw ON rw.id = mp.request_work_id
    WHERE mp.employee_id = me.id
      AND mp.worker_type = 'employee'
      AND mp.is_active = TRUE
      AND rw.current_status = 'during work'
  ) AS is_available,
  EXISTS (
    SELECT 1
    FROM mp_request_work_worker mp
    JOIN request_work rw ON rw.id = mp.request_work_id
    WHERE mp.employee_id = me.id
      AND mp.worker_type = 'employee'
      AND mp.is_active = TRUE
      AND rw.current_status = 'during work'
  ) AS is_working,
  -- ใหม่: ดึง shift_start_ts / shift_end_ts จากกะหมุนเวียน
  CAST(rotation_shift.start_ts AS TEXT) AS shift_start_ts,
  CAST(rotation_shift.end_ts AS TEXT) AS shift_end_ts
FROM
  ms_employee me
  LEFT JOIN ms_code_table mct ON mct.id = me.department_incharge
    AND mct.code = 'department_incharge'
  -- ใหม่: JOIN กะหมุนเวียนแทน ms_shift ตรงๆ
  LEFT JOIN ms_work_group wg ON wg.id = me.work_group_id
  LEFT JOIN LATERAL (
    SELECT s.start_ts, s.end_ts
    FROM ms_shift_pattern sp
    JOIN ms_shift_pattern_week_detail d ON d.pattern_id = sp.id
      AND d.work_group_id = wg.id
      AND d.is_active = TRUE
      AND d.is_deleted = FALSE
    JOIN ms_shift s ON s.id = d.shift_id
    WHERE sp.is_active = TRUE
      AND sp.is_deleted = FALSE
      AND d.week_no = (
        FLOOR(
          ((CURRENT_DATE - sp.anchor_date) % sp.cycle_days)::int / 7
        ) + 1
      )
    LIMIT 1
  ) rotation_shift ON TRUE
WHERE
  me.is_active = TRUE
  AND me.is_deleted = FALSE
ORDER BY
  me.firstname;
```

**หลักการ:**
1. JOIN `ms_work_group` ผ่าน `work_group_id` ของพนักงาน
2. ใช้ LATERAL subquery คำนวณ week_no จาก `anchor_date` + `cycle_days`
3. ดึง `start_ts`/`end_ts` ของกะที่กลุ่มนั้นทำในสัปดาห์นั้น
4. Frontend ใช้ `isInShiftRange()` เหมือนเดิม — ไม่ต้องแก้

### ตรรกะ Off Shift (ต้องแก้ใหม่ — รองรับกะหมุนเวียน)

**เดิม (กะตายตัว):**
```
Off Shift = เวลาปัจจุบัน ไม่อยู่ในช่วง ms_shift.start_ts ~ ms_shift.end_ts
            ของกะที่ผูกกับพนักงาน (ms_employee.work_shift_id → ms_shift.id)
```

**ใหม่ (รองรับกะหมุนเวียน):**
```
1. ดู work_group_id ของพนักงาน → ได้กลุ่ม (เช่น A)
2. ดึง ms_shift_pattern ที่ active → ได้ anchor_date, cycle_days
3. คำนวณ week_no = floor(((วันนี้ - anchor_date) % cycle_days) / 7) + 1
4. ดู ms_shift_pattern_week_detail WHERE week_no = ค่าที่คำนวณ AND work_group_id = กลุ่มพนักงาน
   → ได้ shift_id (เช่น MORNING)
5. ดู ms_shift → ได้ start_ts / end_ts (เช่น 06:00-14:30)
6. เทียบเวลาปัจจุบันกับ start_ts ~ end_ts
7. ถ้าไม่อยู่ในช่วง → is_off_shift = true
```

**SQL ใหม่ (concept):**
```sql
-- คำนวณ is_off_shift จากกะหมุนเวียน
WITH current_pattern AS (
    SELECT id, anchor_date, cycle_days
    FROM ms_shift_pattern
    WHERE is_active = true AND is_deleted = false
    LIMIT 1
),
current_week AS (
    SELECT 
        floor(((CURRENT_DATE - cp.anchor_date) % cp.cycle_days) / 7) + 1 AS week_no,
        cp.id AS pattern_id
    FROM current_pattern cp
)
SELECT 
    me.id AS employee_id,
    CASE 
        WHEN CURRENT_TIME NOT BETWEEN s.start_ts AND s.end_ts THEN true
        ELSE false
    END AS is_off_shift
FROM ms_employee me
JOIN ms_work_group wg ON wg.id = me.work_group_id
JOIN current_week cw ON true
JOIN ms_shift_pattern_week_detail d 
    ON d.pattern_id = cw.pattern_id
    AND d.week_no = cw.week_no
    AND d.work_group_id = wg.id
    AND d.is_active = true
    AND d.is_deleted = false
JOIN ms_shift s ON s.id = d.shift_id
WHERE me.id = :employee_id;
```

> ⚠️ กรณีกะดึก (NIGHT: 23:00-06:00) ที่ข้ามวัน ต้องจัดการ logic เพิ่ม:
> `CURRENT_TIME >= start_ts OR CURRENT_TIME <= end_ts` (แทนที่จะใช้ BETWEEN)

## เลย์เอาต์การ์ดพนักงาน

### โครงสร้างทั่วไป

```
┌─────────────────────────────────┐
│  ชื่อ นามสกุล (bold)        ● ← จุดสี (Status Dot)
│                                 │
│  ⊙ Available                    │  ← ไอคอน + ข้อความสถานะ
└─────────────────────────────────┘
```

- **แถวบน**: ชื่อพนักงาน (ซ้าย) + วงกลมสีทึบ 16x16px (ขวา)
- **แถวล่าง**: ไอคอน 16x16px + ข้อความสถานะ (ชิดซ้าย, gap-2)

### ตัวอย่างการ์ดแต่ละสถานะ

#### ✅ Available (ว่าง) — สีเขียว

```
┌─────────────────────────────────┐
│                                 │
│  Anupong Chaiya             🟢  │  ← bg-green-500
│                                 │
│  ✓ Available                    │  ← CheckCircle + text-green-600
│                                 │
└─────────────────────────────────┘
  border: border-gray-300 (ปกติ)
  bg: bg-white
```

#### 🔧 Working (กำลังทำงาน) — สีน้ำเงิน

```
┌─────────────────────────────────┐
│                                 │
│  Chaiyong Siripong          🔵  │  ← bg-blue-500
│                                 │
│  🕐 Working                     │  ← Clock + text-blue-600
│                                 │
└─────────────────────────────────┘
  border: border-gray-300 (ปกติ)
  bg: bg-white
```

#### 🚫 Off Shift (ไม่อยู่กะ) — สีเทา

```
┌─────────────────────────────────┐
│                                 │
│  Somchai Three              ⚪  │  ← bg-gray-400
│                                 │
│  ⊘ Off Shift                    │  ← MinusCircle + text-gray-400
│                                 │
└─────────────────────────────────┘
  border: border-gray-300 (ปกติ)
  bg: bg-white
```

#### 🔲 เมื่อถูกเลือก (Selected) — ทุกสถานะเหมือนกัน

```
┌═════════════════════════════════┐
│                                 │  ← border-blue-600 (เส้นขอบน้ำเงิน)
│  Sakchai Rattana            🟢  │     bg-blue-50 (พื้นหลังฟ้าอ่อน)
│                                 │
│  ✓ Available                    │
│                                 │
└═════════════════════════════════┘
  border: border-blue-600 (เลือกแล้ว)
  bg: bg-blue-50
```

### ตัวอย่างหน้าจอรวม (Grid 4 คอลัมน์)

```
⚡ Electrician  12
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│ Anupong Chaiya 🟢│  │ Ganya Ratchada 🟢│  │ Sakchai Rattan 🟢│  │ Chaiyong Siri  🔵│
│ ✓ Available      │  │ ✓ Available      │  │ ✓ Available      │  │ 🕐 Working       │
└──────────────────┘  └──────────────────┘  └──────────────────┘  └──────────────────┘

🔧 Mechanic  5
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│ Somchai Three  ⚪│  │ User One       🟢│  │ Prasit Kaew    🔵│  │ Wichai Suk     ⚪│
│ ⊘ Off Shift      │  │ ✓ Available      │  │ 🕐 Working       │  │ ⊘ Off Shift      │
└──────────────────┘  └──────────────────┘  └──────────────────┘  └──────────────────┘
```

### ตัวอย่างปุ่มกรองสถานะ

```
┌───────────────────────────────────────────────────────────────────────────────────┐
│  🔍 Search maintenance staff...     [  All  ] [ Available ] [ Working ] [Off Shift]│
│                                      (red)     (green)       (blue)     (gray)    │
└───────────────────────────────────────────────────────────────────────────────────┘
```

- **All** (แดง): แสดงทุกคน
- **Available** (เขียว): แสดงเฉพาะ `is_available = true` AND `is_off_shift = false`
- **Working** (น้ำเงิน): แสดงเฉพาะ `is_working = true` AND `is_off_shift = false`
- **Off Shift** (เทา): แสดงเฉพาะ `is_off_shift = true`

## สถานะทั้ง 3 แบบ (Priority: Off Shift > Working > Available)

| สถานะ | เงื่อนไข | สี Dot | ไอคอน | ข้อความ | สีข้อความ/ไอคอน |
|---|---|---|---|---|---|
| Off Shift | ไม่อยู่ในเวลากะ (`is_off_shift = true`) | ⚪ bg-gray-400 | MinusCircle | "Off Shift" | text-gray-400 |
| Working | ถูก assign เครื่อง + งานเป็น `during work` (`is_working = true`) | 🔵 bg-blue-500 | Clock | "Working" | text-blue-600 |
| Available | อยู่ในกะ + ไม่ได้ทำงาน (`is_available = true` AND `is_off_shift = false`) | 🟢 bg-green-500 | CheckCircle | "Available" | text-green-600 |

> Priority: ถ้า Off Shift → แสดง Off Shift เสมอ (แม้จะมีงาน assign อยู่)

## ไฟล์ที่เกี่ยวข้อง

### Frontend (Web)

| ไฟล์ | สิ่งที่ต้องทำ |
|---|---|
| `EmployeeItem.tsx` | แทนที่ placeholder ด้วย status dot, icon, label จริง |
| `assign-maintenance.model.ts` | เพิ่ม `is_available`, `is_working`, `is_off_shift` ใน EmployeeRecordData interface |
| `useInteralAssignMaintenanceDrawer.ts` | เพิ่ม filter logic สำหรับ "offShift" โดยใช้ `is_off_shift` |

### Backend (API)

| ไฟล์ | สิ่งที่ต้องทำ |
|---|---|
| `employee.repository.ts` | แก้ SQL คำนวณ `is_off_shift` — เปลี่ยนจากดู `ms_shift` ตรงๆ เป็นคำนวณผ่าน `ms_shift_pattern` + `ms_shift_pattern_week_detail` + `ms_shift` (รองรับกะหมุนเวียน) |
| `employee.model.ts` | เพิ่ม `is_off_shift: boolean` ใน `EmployeeForGroupByWorkType` interface |

## รายการข้อกำหนด (8 ข้อ)

### 1. เพิ่ม/แก้ is_off_shift ใน API (Backend) — รองรับกะหมุนเวียน
- แก้ SQL ใน `employee.repository.ts` เพื่อคำนวณ `is_off_shift` จากกะหมุนเวียน
- Logic ใหม่:
  1. ดึง `work_group_id` ของพนักงาน
  2. ดึง `ms_shift_pattern` ที่ active → ได้ `anchor_date`, `cycle_days`
  3. คำนวณ `week_no` = floor(((วันนี้ - anchor_date) % cycle_days) / 7) + 1
  4. JOIN `ms_shift_pattern_week_detail` ด้วย week_no + work_group_id → ได้ shift_id
  5. JOIN `ms_shift` → ได้ start_ts / end_ts
  6. เทียบเวลาปัจจุบันกับ start_ts ~ end_ts → ถ้าไม่อยู่ในช่วง = Off Shift
- กรณีกะดึก (ข้ามวัน): ใช้ `CURRENT_TIME >= start_ts OR CURRENT_TIME <= end_ts`
- เพิ่ม `is_off_shift: boolean` ใน `EmployeeForGroupByWorkType` interface (API model)

### 2. ขยาย EmployeeRecordData Model (Frontend)
- เพิ่มฟิลด์ `is_available: boolean`, `is_working: boolean`, `is_off_shift: boolean` ใน interface
- ให้ EmployeeItem ใช้งานได้โดยไม่มี type error

### 3. ตรรกะคำนวณสถานะ (Status Derivation Logic)
- `is_off_shift = true` → "offShift" (priority สูงสุด)
- `is_working = true` → "working"
- `is_available = true` AND `is_off_shift = false` → "available"
- Working = ช่างที่ถูก assign เครื่องไว้ + สถานะงาน `during work`
- Off Shift = ไม่อยู่ในเวลาที่ตั้งไว้ใน `ms_shift`

### 4. วงกลมสีสถานะ (Status Dot)
- แสดงจุดกลมทึบ 16x16px ที่มุมขวาบนของการ์ด
- สี: เขียว (available), น้ำเงิน (working), เทา (offShift)

### 5. ไอคอนสถานะ (Status Icon)
- แสดงใต้ชื่อพนักงาน ฝั่งซ้าย ขนาด 16x16px
- ไอคอน: CheckCircle (available), Clock (working), MinusCircle (offShift)
- ใช้ lucide-react

### 6. ข้อความสถานะ (Status Label)
- แสดงคู่กับไอคอน: "Available", "Working", "Off Shift"
- สีตรงกับสถานะ

### 7. เลือกได้ทุกสถานะ — ไม่ล็อก
- พนักงานทุกสถานะ (available, working, offShift) กดเลือกได้หมด
- เมื่อเลือกแล้วแสดง border-blue-600, bg-blue-50 เหมือนกันทุกสถานะ

### 8. ตัวกรอง Off Shift
- เพิ่ม filter logic: เมื่อเลือก "Off Shift" → แสดงเฉพาะพนักงานที่ `is_off_shift = true`
- ปุ่มกรองที่มีอยู่แล้ว: All, Available, Working, Off Shift
