|
1
|
# สรุปข้อกำหนด: ระบบแสดง Icon Status บนการ์ดพนักงาน
|
|
2
|
|
|
3
|
## ภาพรวม
|
|
4
|
|
|
5
|
พัฒนาระบบแสดงไอคอนและป้ายสถานะ (Status Icon & Label) บนการ์ดพนักงานในหน้าเลือกช่างซ่อมบำรุง (AssignMaintenanceSupportDrawer) แทนที่ placeholder "ICON" และ "STATUS" ที่มีอยู่ใน `EmployeeItem` component
|
|
6
|
|
|
7
|
## ที่มาของข้อมูลสถานะ
|
|
8
|
|
|
9
|
ข้อมูลสถานะมาจาก API (`/call-terminal/employee`) ซึ่ง query จาก DB โดยตรง:
|
|
10
|
|
|
11
|
- **is_available** (boolean): `true` = ไม่มีงาน `during work` ที่ active อยู่ (ว่างรับงานได้)
|
|
12
|
- **is_working** (boolean): `true` = มีงาน `during work` ที่ active และไม่ถูก suspend (กำลังทำงานอยู่)
|
|
13
|
- **Off Shift**: คำนวณจากกะหมุนเวียน — ดู `ms_shift_pattern_week_detail` ว่าวันนี้พนักงาน (ตาม work_group_id) ทำกะอะไร แล้วเทียบเวลาปัจจุบันกับ `start_ts` / `end_ts` ของกะนั้น
|
|
14
|
|
|
15
|
> ⚠️ ปัจจุบัน API ยังไม่มี field `is_off_shift` — ต้องเพิ่ม logic คำนวณจาก `ms_shift_pattern` + `ms_shift_pattern_week_detail` + `ms_shift` (ผ่าน `work_group_id` ของพนักงาน)
|
|
16
|
|
|
17
|
### SQL Query ปัจจุบัน (employee.repository.ts)
|
|
18
|
|
|
19
|
```sql
|
|
20
|
-- is_available: ไม่มีงาน during work
|
|
21
|
NOT EXISTS (
|
|
22
|
SELECT 1 FROM mp_request_work_worker mp
|
|
23
|
JOIN request_work rw ON rw.id = mp.request_work_id
|
|
24
|
WHERE mp.employee_id = me.id
|
|
25
|
AND mp.worker_type = 'employee'
|
|
26
|
AND mp.is_active = TRUE
|
|
27
|
AND rw.current_status = 'during work'
|
|
28
|
) AS is_available
|
|
29
|
|
|
30
|
-- is_working: มีงาน during work ที่ไม่ถูก suspend
|
|
31
|
EXISTS (
|
|
32
|
SELECT 1 FROM mp_request_work_worker mp
|
|
33
|
JOIN request_work rw ON rw.id = mp.request_work_id
|
|
34
|
WHERE mp.employee_id = me.id
|
|
35
|
AND mp.worker_type = 'employee'
|
|
36
|
AND mp.is_active = TRUE
|
|
37
|
AND rw.current_status = 'during work'
|
|
38
|
AND rw.is_suspension = FALSE
|
|
39
|
) AS is_working
|
|
40
|
|
|
41
|
-- shift_start_ts / shift_end_ts: ดึงจาก ms_shift ผ่าน work_shift_id (กะตายตัว)
|
|
42
|
CAST(ms.start_ts AS TEXT) AS shift_start_ts,
|
|
43
|
CAST(ms.end_ts AS TEXT) AS shift_end_ts
|
|
44
|
-- JOIN: LEFT JOIN ms_shift ms ON ms.id = me.work_shift_id
|
|
45
|
```
|
|
46
|
|
|
47
|
### ปัญหาของ SQL ปัจจุบัน
|
|
48
|
|
|
49
|
- `shift_start_ts` / `shift_end_ts` ดึงจาก `ms_shift` ผ่าน `work_shift_id` ตรงๆ
|
|
50
|
- ใช้ได้แค่กะตายตัว (เช่น กลุ่ม C ที่ทำ 08:00-17:00 ตลอด)
|
|
51
|
- **ไม่รองรับกะหมุน** — เพราะกลุ่ม A อาจทำกะเช้า (06:00-14:30) สัปดาห์นี้ แต่ทำกะบ่าย (14:30-23:00) สัปดาห์หน้า
|
|
52
|
|
|
53
|
### Frontend Logic ปัจจุบัน (useInteralAssignMaintenanceDrawer.ts)
|
|
54
|
|
|
55
|
- Function `isInShiftRange()` เทียบเวลาปัจจุบันกับ `shift_start_ts`/`shift_end_ts` ที่ได้จาก API
|
|
56
|
- รองรับกะข้ามวัน (overnight shift) อยู่แล้ว
|
|
57
|
- **ไม่ต้องแก้ Frontend** ถ้า Backend ส่ง `shift_start_ts`/`shift_end_ts` ที่ถูกต้องมา
|
|
58
|
|
|
59
|
### SQL ที่ต้องแก้ (รองรับกะหมุนเวียน)
|
|
60
|
|
|
61
|
เปลี่ยนจาก JOIN `ms_shift` ผ่าน `work_shift_id` เป็นคำนวณจาก pattern:
|
|
62
|
|
|
63
|
```sql
|
|
64
|
SELECT
|
|
65
|
me.id,
|
|
66
|
mct.value AS department_incharge_value,
|
|
67
|
me.firstname,
|
|
68
|
me.lastname,
|
|
69
|
NOT EXISTS (
|
|
70
|
SELECT 1
|
|
71
|
FROM mp_request_work_worker mp
|
|
72
|
JOIN request_work rw ON rw.id = mp.request_work_id
|
|
73
|
WHERE mp.employee_id = me.id
|
|
74
|
AND mp.worker_type = 'employee'
|
|
75
|
AND mp.is_active = TRUE
|
|
76
|
AND rw.current_status = 'during work'
|
|
77
|
) AS is_available,
|
|
78
|
EXISTS (
|
|
79
|
SELECT 1
|
|
80
|
FROM mp_request_work_worker mp
|
|
81
|
JOIN request_work rw ON rw.id = mp.request_work_id
|
|
82
|
WHERE mp.employee_id = me.id
|
|
83
|
AND mp.worker_type = 'employee'
|
|
84
|
AND mp.is_active = TRUE
|
|
85
|
AND rw.current_status = 'during work'
|
|
86
|
) AS is_working,
|
|
87
|
-- ใหม่: ดึง shift_start_ts / shift_end_ts จากกะหมุนเวียน
|
|
88
|
CAST(rotation_shift.start_ts AS TEXT) AS shift_start_ts,
|
|
89
|
CAST(rotation_shift.end_ts AS TEXT) AS shift_end_ts
|
|
90
|
FROM
|
|
91
|
ms_employee me
|
|
92
|
LEFT JOIN ms_code_table mct ON mct.id = me.department_incharge
|
|
93
|
AND mct.code = 'department_incharge'
|
|
94
|
-- ใหม่: JOIN กะหมุนเวียนแทน ms_shift ตรงๆ
|
|
95
|
LEFT JOIN ms_work_group wg ON wg.id = me.work_group_id
|
|
96
|
LEFT JOIN LATERAL (
|
|
97
|
SELECT s.start_ts, s.end_ts
|
|
98
|
FROM ms_shift_pattern sp
|
|
99
|
JOIN ms_shift_pattern_week_detail d ON d.pattern_id = sp.id
|
|
100
|
AND d.work_group_id = wg.id
|
|
101
|
AND d.is_active = TRUE
|
|
102
|
AND d.is_deleted = FALSE
|
|
103
|
JOIN ms_shift s ON s.id = d.shift_id
|
|
104
|
WHERE sp.is_active = TRUE
|
|
105
|
AND sp.is_deleted = FALSE
|
|
106
|
AND d.week_no = (
|
|
107
|
FLOOR(
|
|
108
|
((CURRENT_DATE - sp.anchor_date) % sp.cycle_days)::int / 7
|
|
109
|
) + 1
|
|
110
|
)
|
|
111
|
LIMIT 1
|
|
112
|
) rotation_shift ON TRUE
|
|
113
|
WHERE
|
|
114
|
me.is_active = TRUE
|
|
115
|
AND me.is_deleted = FALSE
|
|
116
|
ORDER BY
|
|
117
|
me.firstname;
|
|
118
|
```
|
|
119
|
|
|
120
|
**หลักการ:**
|
|
121
|
1. JOIN `ms_work_group` ผ่าน `work_group_id` ของพนักงาน
|
|
122
|
2. ใช้ LATERAL subquery คำนวณ week_no จาก `anchor_date` + `cycle_days`
|
|
123
|
3. ดึง `start_ts`/`end_ts` ของกะที่กลุ่มนั้นทำในสัปดาห์นั้น
|
|
124
|
4. Frontend ใช้ `isInShiftRange()` เหมือนเดิม — ไม่ต้องแก้
|
|
125
|
|
|
126
|
### ตรรกะ Off Shift (ต้องแก้ใหม่ — รองรับกะหมุนเวียน)
|
|
127
|
|
|
128
|
**เดิม (กะตายตัว):**
|
|
129
|
```
|
|
130
|
Off Shift = เวลาปัจจุบัน ไม่อยู่ในช่วง ms_shift.start_ts ~ ms_shift.end_ts
|
|
131
|
ของกะที่ผูกกับพนักงาน (ms_employee.work_shift_id → ms_shift.id)
|
|
132
|
```
|
|
133
|
|
|
134
|
**ใหม่ (รองรับกะหมุนเวียน):**
|
|
135
|
```
|
|
136
|
1. ดู work_group_id ของพนักงาน → ได้กลุ่ม (เช่น A)
|
|
137
|
2. ดึง ms_shift_pattern ที่ active → ได้ anchor_date, cycle_days
|
|
138
|
3. คำนวณ week_no = floor(((วันนี้ - anchor_date) % cycle_days) / 7) + 1
|
|
139
|
4. ดู ms_shift_pattern_week_detail WHERE week_no = ค่าที่คำนวณ AND work_group_id = กลุ่มพนักงาน
|
|
140
|
→ ได้ shift_id (เช่น MORNING)
|
|
141
|
5. ดู ms_shift → ได้ start_ts / end_ts (เช่น 06:00-14:30)
|
|
142
|
6. เทียบเวลาปัจจุบันกับ start_ts ~ end_ts
|
|
143
|
7. ถ้าไม่อยู่ในช่วง → is_off_shift = true
|
|
144
|
```
|
|
145
|
|
|
146
|
**SQL ใหม่ (concept):**
|
|
147
|
```sql
|
|
148
|
-- คำนวณ is_off_shift จากกะหมุนเวียน
|
|
149
|
WITH current_pattern AS (
|
|
150
|
SELECT id, anchor_date, cycle_days
|
|
151
|
FROM ms_shift_pattern
|
|
152
|
WHERE is_active = true AND is_deleted = false
|
|
153
|
LIMIT 1
|
|
154
|
),
|
|
155
|
current_week AS (
|
|
156
|
SELECT
|
|
157
|
floor(((CURRENT_DATE - cp.anchor_date) % cp.cycle_days) / 7) + 1 AS week_no,
|
|
158
|
cp.id AS pattern_id
|
|
159
|
FROM current_pattern cp
|
|
160
|
)
|
|
161
|
SELECT
|
|
162
|
me.id AS employee_id,
|
|
163
|
CASE
|
|
164
|
WHEN CURRENT_TIME NOT BETWEEN s.start_ts AND s.end_ts THEN true
|
|
165
|
ELSE false
|
|
166
|
END AS is_off_shift
|
|
167
|
FROM ms_employee me
|
|
168
|
JOIN ms_work_group wg ON wg.id = me.work_group_id
|
|
169
|
JOIN current_week cw ON true
|
|
170
|
JOIN ms_shift_pattern_week_detail d
|
|
171
|
ON d.pattern_id = cw.pattern_id
|
|
172
|
AND d.week_no = cw.week_no
|
|
173
|
AND d.work_group_id = wg.id
|
|
174
|
AND d.is_active = true
|
|
175
|
AND d.is_deleted = false
|
|
176
|
JOIN ms_shift s ON s.id = d.shift_id
|
|
177
|
WHERE me.id = :employee_id;
|
|
178
|
```
|
|
179
|
|
|
180
|
> ⚠️ กรณีกะดึก (NIGHT: 23:00-06:00) ที่ข้ามวัน ต้องจัดการ logic เพิ่ม:
|
|
181
|
> `CURRENT_TIME >= start_ts OR CURRENT_TIME <= end_ts` (แทนที่จะใช้ BETWEEN)
|
|
182
|
|
|
183
|
## เลย์เอาต์การ์ดพนักงาน
|
|
184
|
|
|
185
|
### โครงสร้างทั่วไป
|
|
186
|
|
|
187
|
```
|
|
188
|
┌─────────────────────────────────┐
|
|
189
|
│ ชื่อ นามสกุล (bold) ● ← จุดสี (Status Dot)
|
|
190
|
│ │
|
|
191
|
│ ⊙ Available │ ← ไอคอน + ข้อความสถานะ
|
|
192
|
└─────────────────────────────────┘
|
|
193
|
```
|
|
194
|
|
|
195
|
- **แถวบน**: ชื่อพนักงาน (ซ้าย) + วงกลมสีทึบ 16x16px (ขวา)
|
|
196
|
- **แถวล่าง**: ไอคอน 16x16px + ข้อความสถานะ (ชิดซ้าย, gap-2)
|
|
197
|
|
|
198
|
### ตัวอย่างการ์ดแต่ละสถานะ
|
|
199
|
|
|
200
|
#### ✅ Available (ว่าง) — สีเขียว
|
|
201
|
|
|
202
|
```
|
|
203
|
┌─────────────────────────────────┐
|
|
204
|
│ │
|
|
205
|
│ Anupong Chaiya 🟢 │ ← bg-green-500
|
|
206
|
│ │
|
|
207
|
│ ✓ Available │ ← CheckCircle + text-green-600
|
|
208
|
│ │
|
|
209
|
└─────────────────────────────────┘
|
|
210
|
border: border-gray-300 (ปกติ)
|
|
211
|
bg: bg-white
|
|
212
|
```
|
|
213
|
|
|
214
|
#### 🔧 Working (กำลังทำงาน) — สีน้ำเงิน
|
|
215
|
|
|
216
|
```
|
|
217
|
┌─────────────────────────────────┐
|
|
218
|
│ │
|
|
219
|
│ Chaiyong Siripong 🔵 │ ← bg-blue-500
|
|
220
|
│ │
|
|
221
|
│ 🕐 Working │ ← Clock + text-blue-600
|
|
222
|
│ │
|
|
223
|
└─────────────────────────────────┘
|
|
224
|
border: border-gray-300 (ปกติ)
|
|
225
|
bg: bg-white
|
|
226
|
```
|
|
227
|
|
|
228
|
#### 🚫 Off Shift (ไม่อยู่กะ) — สีเทา
|
|
229
|
|
|
230
|
```
|
|
231
|
┌─────────────────────────────────┐
|
|
232
|
│ │
|
|
233
|
│ Somchai Three ⚪ │ ← bg-gray-400
|
|
234
|
│ │
|
|
235
|
│ ⊘ Off Shift │ ← MinusCircle + text-gray-400
|
|
236
|
│ │
|
|
237
|
└─────────────────────────────────┘
|
|
238
|
border: border-gray-300 (ปกติ)
|
|
239
|
bg: bg-white
|
|
240
|
```
|
|
241
|
|
|
242
|
#### 🔲 เมื่อถูกเลือก (Selected) — ทุกสถานะเหมือนกัน
|
|
243
|
|
|
244
|
```
|
|
245
|
┌═════════════════════════════════┐
|
|
246
|
│ │ ← border-blue-600 (เส้นขอบน้ำเงิน)
|
|
247
|
│ Sakchai Rattana 🟢 │ bg-blue-50 (พื้นหลังฟ้าอ่อน)
|
|
248
|
│ │
|
|
249
|
│ ✓ Available │
|
|
250
|
│ │
|
|
251
|
└═════════════════════════════════┘
|
|
252
|
border: border-blue-600 (เลือกแล้ว)
|
|
253
|
bg: bg-blue-50
|
|
254
|
```
|
|
255
|
|
|
256
|
### ตัวอย่างหน้าจอรวม (Grid 4 คอลัมน์)
|
|
257
|
|
|
258
|
```
|
|
259
|
⚡ Electrician 12
|
|
260
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
261
|
│ Anupong Chaiya 🟢│ │ Ganya Ratchada 🟢│ │ Sakchai Rattan 🟢│ │ Chaiyong Siri 🔵│
|
|
262
|
│ ✓ Available │ │ ✓ Available │ │ ✓ Available │ │ 🕐 Working │
|
|
263
|
└──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
264
|
|
|
265
|
🔧 Mechanic 5
|
|
266
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
267
|
│ Somchai Three ⚪│ │ User One 🟢│ │ Prasit Kaew 🔵│ │ Wichai Suk ⚪│
|
|
268
|
│ ⊘ Off Shift │ │ ✓ Available │ │ 🕐 Working │ │ ⊘ Off Shift │
|
|
269
|
└──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘
|
|
270
|
```
|
|
271
|
|
|
272
|
### ตัวอย่างปุ่มกรองสถานะ
|
|
273
|
|
|
274
|
```
|
|
275
|
┌───────────────────────────────────────────────────────────────────────────────────┐
|
|
276
|
│ 🔍 Search maintenance staff... [ All ] [ Available ] [ Working ] [Off Shift]│
|
|
277
|
│ (red) (green) (blue) (gray) │
|
|
278
|
└───────────────────────────────────────────────────────────────────────────────────┘
|
|
279
|
```
|
|
280
|
|
|
281
|
- **All** (แดง): แสดงทุกคน
|
|
282
|
- **Available** (เขียว): แสดงเฉพาะ `is_available = true` AND `is_off_shift = false`
|
|
283
|
- **Working** (น้ำเงิน): แสดงเฉพาะ `is_working = true` AND `is_off_shift = false`
|
|
284
|
- **Off Shift** (เทา): แสดงเฉพาะ `is_off_shift = true`
|
|
285
|
|
|
286
|
## สถานะทั้ง 3 แบบ (Priority: Off Shift > Working > Available)
|
|
287
|
|
|
288
|
| สถานะ | เงื่อนไข | สี Dot | ไอคอน | ข้อความ | สีข้อความ/ไอคอน |
|
|
289
|
|---|---|---|---|---|---|
|
|
290
|
| Off Shift | ไม่อยู่ในเวลากะ (`is_off_shift = true`) | ⚪ bg-gray-400 | MinusCircle | "Off Shift" | text-gray-400 |
|
|
291
|
| Working | ถูก assign เครื่อง + งานเป็น `during work` (`is_working = true`) | 🔵 bg-blue-500 | Clock | "Working" | text-blue-600 |
|
|
292
|
| Available | อยู่ในกะ + ไม่ได้ทำงาน (`is_available = true` AND `is_off_shift = false`) | 🟢 bg-green-500 | CheckCircle | "Available" | text-green-600 |
|
|
293
|
|
|
294
|
> Priority: ถ้า Off Shift → แสดง Off Shift เสมอ (แม้จะมีงาน assign อยู่)
|
|
295
|
|
|
296
|
## ไฟล์ที่เกี่ยวข้อง
|
|
297
|
|
|
298
|
### Frontend (Web)
|
|
299
|
|
|
300
|
| ไฟล์ | สิ่งที่ต้องทำ |
|
|
301
|
|---|---|
|
|
302
|
| `EmployeeItem.tsx` | แทนที่ placeholder ด้วย status dot, icon, label จริง |
|
|
303
|
| `assign-maintenance.model.ts` | เพิ่ม `is_available`, `is_working`, `is_off_shift` ใน EmployeeRecordData interface |
|
|
304
|
| `useInteralAssignMaintenanceDrawer.ts` | เพิ่ม filter logic สำหรับ "offShift" โดยใช้ `is_off_shift` |
|
|
305
|
|
|
306
|
### Backend (API)
|
|
307
|
|
|
308
|
| ไฟล์ | สิ่งที่ต้องทำ |
|
|
309
|
|---|---|
|
|
310
|
| `employee.repository.ts` | แก้ SQL คำนวณ `is_off_shift` — เปลี่ยนจากดู `ms_shift` ตรงๆ เป็นคำนวณผ่าน `ms_shift_pattern` + `ms_shift_pattern_week_detail` + `ms_shift` (รองรับกะหมุนเวียน) |
|
|
311
|
| `employee.model.ts` | เพิ่ม `is_off_shift: boolean` ใน `EmployeeForGroupByWorkType` interface |
|
|
312
|
|
|
313
|
## รายการข้อกำหนด (8 ข้อ)
|
|
314
|
|
|
315
|
### 1. เพิ่ม/แก้ is_off_shift ใน API (Backend) — รองรับกะหมุนเวียน
|
|
316
|
- แก้ SQL ใน `employee.repository.ts` เพื่อคำนวณ `is_off_shift` จากกะหมุนเวียน
|
|
317
|
- Logic ใหม่:
|
|
318
|
1. ดึง `work_group_id` ของพนักงาน
|
|
319
|
2. ดึง `ms_shift_pattern` ที่ active → ได้ `anchor_date`, `cycle_days`
|
|
320
|
3. คำนวณ `week_no` = floor(((วันนี้ - anchor_date) % cycle_days) / 7) + 1
|
|
321
|
4. JOIN `ms_shift_pattern_week_detail` ด้วย week_no + work_group_id → ได้ shift_id
|
|
322
|
5. JOIN `ms_shift` → ได้ start_ts / end_ts
|
|
323
|
6. เทียบเวลาปัจจุบันกับ start_ts ~ end_ts → ถ้าไม่อยู่ในช่วง = Off Shift
|
|
324
|
- กรณีกะดึก (ข้ามวัน): ใช้ `CURRENT_TIME >= start_ts OR CURRENT_TIME <= end_ts`
|
|
325
|
- เพิ่ม `is_off_shift: boolean` ใน `EmployeeForGroupByWorkType` interface (API model)
|
|
326
|
|
|
327
|
### 2. ขยาย EmployeeRecordData Model (Frontend)
|
|
328
|
- เพิ่มฟิลด์ `is_available: boolean`, `is_working: boolean`, `is_off_shift: boolean` ใน interface
|
|
329
|
- ให้ EmployeeItem ใช้งานได้โดยไม่มี type error
|
|
330
|
|
|
331
|
### 3. ตรรกะคำนวณสถานะ (Status Derivation Logic)
|
|
332
|
- `is_off_shift = true` → "offShift" (priority สูงสุด)
|
|
333
|
- `is_working = true` → "working"
|
|
334
|
- `is_available = true` AND `is_off_shift = false` → "available"
|
|
335
|
- Working = ช่างที่ถูก assign เครื่องไว้ + สถานะงาน `during work`
|
|
336
|
- Off Shift = ไม่อยู่ในเวลาที่ตั้งไว้ใน `ms_shift`
|
|
337
|
|
|
338
|
### 4. วงกลมสีสถานะ (Status Dot)
|
|
339
|
- แสดงจุดกลมทึบ 16x16px ที่มุมขวาบนของการ์ด
|
|
340
|
- สี: เขียว (available), น้ำเงิน (working), เทา (offShift)
|
|
341
|
|
|
342
|
### 5. ไอคอนสถานะ (Status Icon)
|
|
343
|
- แสดงใต้ชื่อพนักงาน ฝั่งซ้าย ขนาด 16x16px
|
|
344
|
- ไอคอน: CheckCircle (available), Clock (working), MinusCircle (offShift)
|
|
345
|
- ใช้ lucide-react
|
|
346
|
|
|
347
|
### 6. ข้อความสถานะ (Status Label)
|
|
348
|
- แสดงคู่กับไอคอน: "Available", "Working", "Off Shift"
|
|
349
|
- สีตรงกับสถานะ
|
|
350
|
|
|
351
|
### 7. เลือกได้ทุกสถานะ — ไม่ล็อก
|
|
352
|
- พนักงานทุกสถานะ (available, working, offShift) กดเลือกได้หมด
|
|
353
|
- เมื่อเลือกแล้วแสดง border-blue-600, bg-blue-50 เหมือนกันทุกสถานะ
|
|
354
|
|
|
355
|
### 8. ตัวกรอง Off Shift
|
|
356
|
- เพิ่ม filter logic: เมื่อเลือก "Off Shift" → แสดงเฉพาะพนักงานที่ `is_off_shift = true`
|
|
357
|
- ปุ่มกรองที่มีอยู่แล้ว: All, Available, Working, Off Shift
|