Add /api/attendance/status_texts endpoint; dynamic status filter in Attendance List (filter by status_text LIKE)
This commit is contained in:
@@ -1098,6 +1098,15 @@ async def dashboard_summary(
|
|||||||
"staff_count": len(staff_set),
|
"staff_count": len(staff_set),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@app.get("/api/attendance/status_texts")
|
||||||
|
async def attendance_status_texts(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Return distinct status_text values for filter dropdown."""
|
||||||
|
rows = db.query(AttendanceRecord.status_text).distinct().all()
|
||||||
|
return [r[0] for r in rows if r[0]]
|
||||||
|
|
||||||
@app.get("/api/attendance/stats")
|
@app.get("/api/attendance/stats")
|
||||||
async def attendance_stats(
|
async def attendance_stats(
|
||||||
date_from: Optional[str] = None,
|
date_from: Optional[str] = None,
|
||||||
@@ -1234,6 +1243,7 @@ async def attendance_records(
|
|||||||
date_to: Optional[str] = None,
|
date_to: Optional[str] = None,
|
||||||
staff: Optional[str] = None,
|
staff: Optional[str] = None,
|
||||||
status: Optional[str] = None,
|
status: Optional[str] = None,
|
||||||
|
status_text: Optional[str] = None,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -1256,6 +1266,8 @@ async def attendance_records(
|
|||||||
q = q.filter(AttendanceRecord.employee_name == staff)
|
q = q.filter(AttendanceRecord.employee_name == staff)
|
||||||
if status:
|
if status:
|
||||||
q = q.filter(AttendanceRecord.status_code == status)
|
q = q.filter(AttendanceRecord.status_code == status)
|
||||||
|
if status_text:
|
||||||
|
q = q.filter(AttendanceRecord.status_text.like(f"%{status_text}%"))
|
||||||
if search:
|
if search:
|
||||||
like = f"%{search}%"
|
like = f"%{search}%"
|
||||||
q = q.filter(or_(
|
q = q.filter(or_(
|
||||||
|
|||||||
@@ -17,15 +17,24 @@ export default function AttendanceList() {
|
|||||||
const [staffFilter, setStaffFilter] = useState('')
|
const [staffFilter, setStaffFilter] = useState('')
|
||||||
const [statusFilter, setStatusFilter] = useState('')
|
const [statusFilter, setStatusFilter] = useState('')
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
|
const [statusOptions, setStatusOptions] = useState([])
|
||||||
const perPage = 50
|
const perPage = 50
|
||||||
|
|
||||||
const [staffList, setStaffList] = useState([])
|
const [staffList, setStaffList] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRecords()
|
fetchRecords()
|
||||||
|
fetchStatusTexts()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [page, sortBy, sortOrder, dateFrom, dateTo, staffFilter, statusFilter, search])
|
}, [page, sortBy, sortOrder, dateFrom, dateTo, staffFilter, statusFilter, search])
|
||||||
|
|
||||||
|
const fetchStatusTexts = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/api/attendance/status_texts')
|
||||||
|
setStatusOptions(data || [])
|
||||||
|
} catch (e) { console.error('Failed to fetch status texts', e) }
|
||||||
|
}
|
||||||
|
|
||||||
const fetchRecords = async () => {
|
const fetchRecords = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -39,7 +48,7 @@ export default function AttendanceList() {
|
|||||||
if (dateFrom) params.append('date_from', dateFrom)
|
if (dateFrom) params.append('date_from', dateFrom)
|
||||||
if (dateTo) params.append('date_to', dateTo)
|
if (dateTo) params.append('date_to', dateTo)
|
||||||
if (staffFilter) params.append('staff', staffFilter)
|
if (staffFilter) params.append('staff', staffFilter)
|
||||||
if (statusFilter) params.append('status', statusFilter)
|
if (statusFilter) params.append('status_text', statusFilter)
|
||||||
|
|
||||||
const { data } = await api.get(`/api/attendance/records?${params}`)
|
const { data } = await api.get(`/api/attendance/records?${params}`)
|
||||||
setRecords(data.records || [])
|
setRecords(data.records || [])
|
||||||
@@ -175,17 +184,18 @@ export default function AttendanceList() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-slate-500">狀態:</span>
|
<span className="text-xs text-slate-500">狀態:</span>
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter ? statusFilter : (statusTextFilter || '')}
|
||||||
onChange={(e) => { setStatusFilter(e.target.value); setPage(1) }}
|
onChange={(e) => {
|
||||||
|
setStatusFilter(e.target.value)
|
||||||
|
setStatusTextFilter('')
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
className="text-sm border border-slate-300 rounded-md px-2 py-1.5"
|
className="text-sm border border-slate-300 rounded-md px-2 py-1.5"
|
||||||
>
|
>
|
||||||
<option value="">全部</option>
|
<option value="">全部</option>
|
||||||
<option value="normal">正常</option>
|
{statusOptions.map(opt => (
|
||||||
<option value="late">遲到</option>
|
<option key={opt} value={opt}>{opt}</option>
|
||||||
<option value="early">早退</option>
|
))}
|
||||||
<option value="ot">OT</option>
|
|
||||||
<option value="abnormal">異常</option>
|
|
||||||
<option value="missing">缺勤</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user