This commit is contained in:
@@ -236,71 +236,69 @@ export const AttendancePanel: React.FC<AttendancePanelProps> = ({ classId }) =>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<div
|
||||
className={`attendance-qldt-banner${
|
||||
qldtSynced ? (qldtDirty ? ' attendance-qldt-banner--stale' : ' attendance-qldt-banner--synced') : ''
|
||||
}`}
|
||||
>
|
||||
{qldtSynced ? (
|
||||
qldtDirty ? (
|
||||
<>
|
||||
<strong>Đã lưu QLĐT — có thay đổi mới</strong>
|
||||
<span>
|
||||
Lần đẩy: {formatQldtTime(shiftInfo?.pushedToQldtAt)} · Cần đẩy lại sau khi chỉnh sửa
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>Đã lưu QLĐT</strong>
|
||||
<span>Lần đẩy gần nhất: {formatQldtTime(shiftInfo?.pushedToQldtAt)}</span>
|
||||
</>
|
||||
)
|
||||
<div className={`attendance-collapsible-wrapper ${isCollapsed ? 'collapsed' : ''}`}>
|
||||
<div
|
||||
className={`attendance-qldt-banner${
|
||||
qldtSynced ? (qldtDirty ? ' attendance-qldt-banner--stale' : ' attendance-qldt-banner--synced') : ''
|
||||
}`}
|
||||
>
|
||||
{qldtSynced ? (
|
||||
qldtDirty ? (
|
||||
<>
|
||||
<strong>Đã lưu QLĐT — có thay đổi mới</strong>
|
||||
<span>
|
||||
Lần đẩy: {formatQldtTime(shiftInfo?.pushedToQldtAt)} · Cần đẩy lại sau khi chỉnh sửa
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>Chưa đẩy lên QLĐT</strong>
|
||||
<span>Ca {period} ngày {date} — bấm "Đẩy QLĐT" sau khi kiểm tra trạng thái</span>
|
||||
<strong>Đã lưu QLĐT</strong>
|
||||
<span>Lần đẩy gần nhất: {formatQldtTime(shiftInfo?.pushedToQldtAt)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{currentShift && (
|
||||
<div className="attendance-shift-info">
|
||||
<span>{currentShift.startTime}–{currentShift.endTime}</span>
|
||||
<span>{currentShift.courseName || 'Chưa chọn môn'}</span>
|
||||
{!currentShift.isActive && <span className="badge badge-muted">Ca tắt</span>}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<strong>Chưa đẩy lên QLĐT</strong>
|
||||
<span>Ca {period} ngày {date} — bấm "Đẩy QLĐT" sau khi kiểm tra trạng thái</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="attendance-status-summary">
|
||||
{ATTENDANCE_STATUS_OPTIONS.map(opt => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
className={`attendance-summary-chip ${attendanceStatusClass(opt.value)}`}
|
||||
onClick={() => setSearchQuery('')}
|
||||
title={`${opt.label}: ${statusCounts[opt.value] ?? 0} sinh viên`}
|
||||
>
|
||||
<span className="attendance-summary-chip-label">{opt.short}</span>
|
||||
<span className="attendance-summary-chip-count">{statusCounts[opt.value] ?? 0}</span>
|
||||
</button>
|
||||
))}
|
||||
<span className="attendance-summary-total">
|
||||
{filteredRows.length}/{rows.length} SV
|
||||
{searchQuery.trim() ? ' (đã lọc)' : ''}
|
||||
</span>
|
||||
{currentShift && (
|
||||
<div className="attendance-shift-info">
|
||||
<span>{currentShift.startTime}–{currentShift.endTime}</span>
|
||||
<span>{currentShift.courseName || 'Chưa chọn môn'}</span>
|
||||
{!currentShift.isActive && <span className="badge badge-muted">Ca tắt</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="schedule-hint" style={{ margin: '0 0 8px 0' }}>
|
||||
Sửa trạng thái thủ công sẽ được khóa — hệ thống tự tính sẽ không ghi đè.
|
||||
</p>
|
||||
<div className="attendance-status-summary">
|
||||
{ATTENDANCE_STATUS_OPTIONS.map(opt => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
className={`attendance-summary-chip ${attendanceStatusClass(opt.value)}`}
|
||||
onClick={() => setSearchQuery('')}
|
||||
title={`${opt.label}: ${statusCounts[opt.value] ?? 0} sinh viên`}
|
||||
>
|
||||
<span className="attendance-summary-chip-label">{opt.short}</span>
|
||||
<span className="attendance-summary-chip-count">{statusCounts[opt.value] ?? 0}</span>
|
||||
</button>
|
||||
))}
|
||||
<span className="attendance-summary-total">
|
||||
{filteredRows.length}/{rows.length} SV
|
||||
{searchQuery.trim() ? ' (đã lọc)' : ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '0.65rem 0.85rem', background: '#fef3c7', border: '1px solid #fcd34d', borderRadius: '4px', color: '#92400e', fontSize: '0.82rem', display: 'flex', alignItems: 'center', gap: '0.35rem', margin: '4px 0 10px 0', lineHeight: '1.4' }}>
|
||||
💡 <strong>Lưu ý:</strong> Hệ thống hiện tại chỉ ghi nhận điểm danh học tập nội bộ và <strong>KHÔNG tự động lưu lên QLĐT</strong>. Thầy cô vui lòng kiểm tra kỹ trạng thái của sinh viên, sau đó bấm nút <strong>Đẩy QLĐT</strong> ở góc phải bên trên để đồng bộ điểm danh chính thức.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p className="schedule-hint" style={{ margin: '0 0 4px 0' }}>
|
||||
Sửa trạng thái thủ công sẽ được khóa — hệ thống tự tính sẽ không ghi đè.
|
||||
</p>
|
||||
|
||||
<div style={{ padding: '0.65rem 0.85rem', background: '#fef3c7', border: '1px solid #fcd34d', borderRadius: '4px', color: '#92400e', fontSize: '0.82rem', display: 'flex', alignItems: 'center', gap: '0.35rem', margin: '4px 0 4px 0', lineHeight: '1.4' }}>
|
||||
💡 <strong>Lưu ý:</strong> Hệ thống hiện tại chỉ ghi nhận điểm danh học tập nội bộ và <strong>KHÔNG tự động lưu lên QLĐT</strong>. Thầy cô vui lòng kiểm tra kỹ trạng thái của sinh viên, sau đó bấm nút <strong>Đẩy QLĐT</strong> ở góc phải bên trên để đồng bộ điểm danh chính thức.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedStudentRkIds.length > 0 && (
|
||||
<div className="attendance-bulk-actions">
|
||||
|
||||
@@ -4653,10 +4653,13 @@ input:checked + .slider:before {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── System management ── */
|
||||
.system-page {
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.system-subnav {
|
||||
@@ -4728,20 +4731,30 @@ input:checked + .slider:before {
|
||||
}
|
||||
|
||||
.system-panel {
|
||||
min-height: 200px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.system-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.system-section--grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 280px;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.system-block {
|
||||
@@ -4750,6 +4763,9 @@ input:checked + .slider:before {
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1.15rem 1.25rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.system-block--info {
|
||||
@@ -4825,6 +4841,9 @@ input:checked + .slider:before {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.system-domain-item {
|
||||
@@ -4890,7 +4909,10 @@ input:checked + .slider:before {
|
||||
|
||||
.system-table-wrap {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0.75rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
@@ -5414,3 +5436,21 @@ input:checked + .slider:before {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.attendance-collapsible-wrapper {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-in-out, opacity 0.25s ease-in-out, margin 0.3s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.attendance-collapsible-wrapper.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user