feat: complete LOW priority code quality improvements
Backend: - LOW-002: Add Query validation with max page size limits (100) - LOW-003: Replace magic strings with TaskStatus.is_done flag - LOW-004: Add 'creation' trigger type validation - Add action_executor.py with UpdateFieldAction and AutoAssignAction Frontend: - LOW-005: Replace TypeScript 'any' with 'unknown' + type guards - LOW-006: Add ConfirmModal component with A11Y support - LOW-007: Add ToastContext for user feedback notifications - LOW-009: Add Skeleton components (17 loading states replaced) - LOW-010: Setup Vitest with 21 tests for ConfirmModal and Skeleton Components updated: - App.tsx, ProtectedRoute.tsx, Spaces.tsx, Projects.tsx, Tasks.tsx - ProjectSettings.tsx, AuditPage.tsx, WorkloadPage.tsx, ProjectHealthPage.tsx - Comments.tsx, AttachmentList.tsx, TriggerList.tsx, TaskDetailModal.tsx - NotificationBell.tsx, BlockerDialog.tsx, CalendarView.tsx, WorkloadUserDetail.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { UserWorkloadDetail, LoadLevel, workloadApi } from '../services/workload'
|
||||
import { SkeletonList } from './Skeleton'
|
||||
|
||||
interface WorkloadUserDetailProps {
|
||||
userId: string
|
||||
@@ -34,14 +35,27 @@ export function WorkloadUserDetail({
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [detail, setDetail] = useState<UserWorkloadDetail | null>(null)
|
||||
const modalOverlayRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// A11Y: Handle Escape key to close modal
|
||||
useEffect(() => {
|
||||
if (isOpen && userId) {
|
||||
loadUserDetail()
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}, [isOpen, userId, weekStart])
|
||||
|
||||
const loadUserDetail = async () => {
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
modalOverlayRef.current?.focus()
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [isOpen, onClose])
|
||||
|
||||
const loadUserDetail = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
@@ -53,7 +67,13 @@ export function WorkloadUserDetail({
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [userId, weekStart])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && userId) {
|
||||
loadUserDetail()
|
||||
}
|
||||
}, [isOpen, userId, weekStart, loadUserDetail])
|
||||
|
||||
const formatDate = (dateStr: string | null) => {
|
||||
if (!dateStr) return '-'
|
||||
@@ -64,11 +84,19 @@ export function WorkloadUserDetail({
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div style={styles.overlay} onClick={onClose}>
|
||||
<div
|
||||
ref={modalOverlayRef}
|
||||
style={styles.overlay}
|
||||
onClick={onClose}
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="workload-detail-title"
|
||||
>
|
||||
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div style={styles.header}>
|
||||
<div>
|
||||
<h2 style={styles.title}>{userName}</h2>
|
||||
<h2 id="workload-detail-title" style={styles.title}>{userName}</h2>
|
||||
<span style={styles.subtitle}>Workload Details</span>
|
||||
</div>
|
||||
<button style={styles.closeButton} onClick={onClose} aria-label="Close">
|
||||
@@ -77,7 +105,9 @@ export function WorkloadUserDetail({
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={styles.loading}>Loading...</div>
|
||||
<div style={{ padding: '16px' }}>
|
||||
<SkeletonList count={5} showAvatar={false} />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div style={styles.error}>{error}</div>
|
||||
) : detail ? (
|
||||
@@ -203,7 +233,7 @@ const styles: { [key: string]: React.CSSProperties } = {
|
||||
border: 'none',
|
||||
fontSize: '28px',
|
||||
cursor: 'pointer',
|
||||
color: '#999',
|
||||
color: '#767676', // WCAG AA compliant
|
||||
padding: '0',
|
||||
lineHeight: 1,
|
||||
},
|
||||
@@ -265,7 +295,7 @@ const styles: { [key: string]: React.CSSProperties } = {
|
||||
emptyTasks: {
|
||||
textAlign: 'center',
|
||||
padding: '24px',
|
||||
color: '#999',
|
||||
color: '#767676', // WCAG AA compliant
|
||||
fontSize: '14px',
|
||||
},
|
||||
taskList: {
|
||||
|
||||
Reference in New Issue
Block a user