This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
|
||||
interface AttendancePanelProps {
|
||||
classId: number;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function formatQldtTime(iso?: string): string {
|
||||
@@ -28,7 +29,7 @@ function formatQldtTime(iso?: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) => {
|
||||
export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId, onClose }) => {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const [date, setDate] = useState(today);
|
||||
const [period, setPeriod] = useState(1);
|
||||
@@ -231,7 +232,25 @@ export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) =>
|
||||
const qldtDirty = Boolean(shiftInfo?.qldtDirty);
|
||||
|
||||
return (
|
||||
<div className="attendance-panel">
|
||||
<div className="modal-overlay attendance-modal-overlay">
|
||||
<div className="modal-container attendance-modal-container">
|
||||
<div className="modal-header attendance-modal-header" style={{ padding: '0.75rem 1.25rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h3 className="modal-title" style={{ margin: 0, fontSize: '1.1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
📋 Quản lý điểm danh ca học
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close-btn"
|
||||
style={{ fontSize: '1.5rem', border: 'none', background: 'none', color: 'var(--text-muted)', cursor: 'pointer', padding: '0 0.5rem' }}
|
||||
onClick={onClose}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body attendance-modal-body" style={{ padding: '1rem', display: 'flex', flexDirection: 'column', gap: '0.75rem', flex: 1, minHeight: 0, overflowY: 'auto' }}>
|
||||
<div className="attendance-toolbar">
|
||||
<label className="attendance-field">
|
||||
<span>Ngày</span>
|
||||
@@ -370,13 +389,13 @@ export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) =>
|
||||
|
||||
{/* Leave Requests Approval Modal */}
|
||||
{showLeaveModal && courseId && (
|
||||
<div className="modal-overlay" onClick={() => setShowLeaveModal(false)}>
|
||||
<div className="modal-overlay" style={{ zIndex: 200 }} onClick={() => setShowLeaveModal(false)}>
|
||||
<div className="modal-container" style={{ maxWidth: '680px', width: '95%', display: 'flex', flexDirection: 'column' }} onClick={e => e.stopPropagation()}>
|
||||
<div className="modal-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h3 className="modal-title" style={{ margin: 0 }}>✉️ Đơn xin nghỉ phép trong ca</h3>
|
||||
<p style={{ margin: '4px 0 0 0', color: 'var(--text-secondary)', fontSize: '0.8rem' }}>
|
||||
Ca {period} ngày {date} - Giao diện duyệt đơn xin nghỉ từ cổng QLĐT portal.
|
||||
Ca {period} ngày {date} - Giao diện duyệt đơn xin nghỉ học.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
@@ -395,6 +414,11 @@ export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) =>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-body" style={{ padding: '1rem', overflowY: 'auto', maxHeight: '60vh', display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||
<div style={{ padding: '0.65rem 0.85rem', background: '#e0f2fe', border: '1px solid #bae6fd', borderRadius: '6px', color: '#0369a1', fontSize: '0.82rem', display: 'flex', flexDirection: 'column', gap: '0.25rem', lineHeight: '1.4' }}>
|
||||
<div>💡 <strong>Lưu ý duyệt đơn phép:</strong> Thao tác duyệt/từ chối đơn tại đây chỉ được ghi nhận <strong>nội bộ trên hệ thống Simple Care</strong> (không đồng bộ ngược lên QLĐT).</div>
|
||||
<div style={{ paddingLeft: '1.15rem' }}>• Khi duyệt đơn, hệ thống sẽ tự động đổi trạng thái điểm danh của sinh viên này thành <strong>"Nghỉ có phép"</strong>.</div>
|
||||
<div style={{ paddingLeft: '1.15rem' }}>• Trạng thái này sẽ được cập nhật lên QLĐT khi thầy cô bấm nút <strong>"Đẩy QLĐT"</strong> ở giao diện điểm danh ca học.</div>
|
||||
</div>
|
||||
{loadingLeave ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '2rem 0' }}>
|
||||
<div className="sync-spinner" style={{ width: 28, height: 28 }} />
|
||||
@@ -624,6 +648,8 @@ export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) =>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -446,7 +446,7 @@ export const ClassWorkspace: React.FC<ClassWorkspaceProps> = ({ classId, sourceT
|
||||
|
||||
<div className={`workspace-panel-body ${activeSubTab === 'attendance' || activeSubTab === 'logs' || activeSubTab === 'grid' || activeSubTab === 'roster' || activeSubTab === 'violations' ? 'workspace-panel-fill' : ''}`}>
|
||||
{activeSubTab === 'attendance' ? (
|
||||
<AttendancePanel classId={classId} />
|
||||
<AttendancePanel classId={classId} onClose={() => setActiveSubTab('roster')} />
|
||||
) : activeSubTab === 'violations' ? (
|
||||
<ViolationsPanel mode="class" classId={classId} />
|
||||
) : activeSubTab === 'grid' ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
apiFetchClassViolations,
|
||||
apiFetchExamViolations,
|
||||
@@ -24,7 +24,7 @@ function formatTime(iso?: string): string {
|
||||
return d.toLocaleString('vi-VN');
|
||||
}
|
||||
|
||||
export const ViolationsPanel: React.FC<Props> = (props) => {
|
||||
export const ViolationsPanel = (props: Props) => {
|
||||
const [date, setDate] = useState(todayLocal);
|
||||
const [kind, setKind] = useState('');
|
||||
const [rows, setRows] = useState<StudentViolationItem[]>([]);
|
||||
@@ -54,6 +54,23 @@ export const ViolationsPanel: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div className="session-logs-panel" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', height: '100%' }}>
|
||||
{props.mode === 'exam' && (
|
||||
<div style={{
|
||||
padding: '0.65rem 0.85rem',
|
||||
background: '#fef3c7',
|
||||
border: '1px solid #fcd34d',
|
||||
borderRadius: '6px',
|
||||
color: '#92400e',
|
||||
fontSize: '0.82rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.35rem',
|
||||
margin: '0',
|
||||
lineHeight: '1.4'
|
||||
}}>
|
||||
⚠️ <strong>Lưu ý:</strong> Chức năng theo dõi vi phạm đang được theo dõi đánh giá tính chuẩn xác, hiện tại kết quả chạy thử chỉ mang tính chất tham khảo thu thập dữ liệu theo các loại máy.
|
||||
</div>
|
||||
)}
|
||||
<div className="attendance-toolbar" style={{ marginBottom: 0, flexWrap: 'wrap' }}>
|
||||
<label className="attendance-field">
|
||||
<span>Ngày</span>
|
||||
|
||||
@@ -2037,6 +2037,40 @@ input:checked + .slider:before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attendance-modal-overlay {
|
||||
z-index: 150 !important;
|
||||
background-color: rgba(15, 23, 42, 0.6) !important;
|
||||
padding: 1.5rem;
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attendance-modal-container {
|
||||
width: 98vw !important;
|
||||
height: 96vh !important;
|
||||
max-width: 1440px !important;
|
||||
max-height: 96vh !important;
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--bg-app);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
border: 1px solid var(--border-color);
|
||||
animation: modalScaleUp 0.2s ease-out;
|
||||
}
|
||||
|
||||
.attendance-modal-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.attendance-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user