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,11 +1,13 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import api from '../services/api'
|
||||
import { Comments } from './Comments'
|
||||
import { TaskAttachments } from './TaskAttachments'
|
||||
import { SubtaskList } from './SubtaskList'
|
||||
import { UserSelect } from './UserSelect'
|
||||
import { UserSearchResult } from '../services/collaboration'
|
||||
import { customFieldsApi, CustomField, CustomValueResponse } from '../services/customFields'
|
||||
import { CustomFieldInput } from './CustomFieldInput'
|
||||
import { SkeletonList } from './Skeleton'
|
||||
|
||||
interface Task {
|
||||
id: string
|
||||
@@ -37,6 +39,7 @@ interface TaskDetailModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onUpdate: () => void
|
||||
onSubtaskClick?: (subtaskId: string) => void
|
||||
}
|
||||
|
||||
export function TaskDetailModal({
|
||||
@@ -45,6 +48,7 @@ export function TaskDetailModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onUpdate,
|
||||
onSubtaskClick,
|
||||
}: TaskDetailModalProps) {
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -69,14 +73,7 @@ export function TaskDetailModal({
|
||||
const [editCustomValues, setEditCustomValues] = useState<Record<string, string | number | null>>({})
|
||||
const [loadingCustomFields, setLoadingCustomFields] = useState(false)
|
||||
|
||||
// Load custom fields for the project
|
||||
useEffect(() => {
|
||||
if (task.project_id) {
|
||||
loadCustomFields()
|
||||
}
|
||||
}, [task.project_id])
|
||||
|
||||
const loadCustomFields = async () => {
|
||||
const loadCustomFields = useCallback(async () => {
|
||||
setLoadingCustomFields(true)
|
||||
try {
|
||||
const response = await customFieldsApi.getCustomFields(task.project_id)
|
||||
@@ -86,7 +83,14 @@ export function TaskDetailModal({
|
||||
} finally {
|
||||
setLoadingCustomFields(false)
|
||||
}
|
||||
}
|
||||
}, [task.project_id])
|
||||
|
||||
// Load custom fields for the project
|
||||
useEffect(() => {
|
||||
if (task.project_id) {
|
||||
loadCustomFields()
|
||||
}
|
||||
}, [task.project_id, loadCustomFields])
|
||||
|
||||
// Initialize custom values from task
|
||||
useEffect(() => {
|
||||
@@ -120,6 +124,28 @@ export function TaskDetailModal({
|
||||
setIsEditing(false)
|
||||
}, [task])
|
||||
|
||||
// Reference to the modal overlay for focus management
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Handle Escape key to close modal
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
// Focus the overlay for keyboard accessibility
|
||||
overlayRef.current?.focus()
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [isOpen, onClose])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const handleSave = async () => {
|
||||
@@ -204,7 +230,15 @@ export function TaskDetailModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.overlay} onClick={handleOverlayClick}>
|
||||
<div
|
||||
ref={overlayRef}
|
||||
style={styles.overlay}
|
||||
onClick={handleOverlayClick}
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="task-detail-title"
|
||||
>
|
||||
<div style={styles.modal}>
|
||||
<div style={styles.header}>
|
||||
<div style={styles.headerLeft}>
|
||||
@@ -223,7 +257,7 @@ export function TaskDetailModal({
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<h2 style={styles.title}>{task.title}</h2>
|
||||
<h2 id="task-detail-title" style={styles.title}>{task.title}</h2>
|
||||
)}
|
||||
</div>
|
||||
<div style={styles.headerActions}>
|
||||
@@ -285,6 +319,16 @@ export function TaskDetailModal({
|
||||
<div style={styles.section}>
|
||||
<TaskAttachments taskId={task.id} />
|
||||
</div>
|
||||
|
||||
{/* Subtasks Section */}
|
||||
<div style={styles.section}>
|
||||
<SubtaskList
|
||||
taskId={task.id}
|
||||
projectId={task.project_id}
|
||||
onSubtaskClick={onSubtaskClick}
|
||||
onSubtaskCreated={onUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.sidebar}>
|
||||
@@ -420,7 +464,7 @@ export function TaskDetailModal({
|
||||
<div style={styles.customFieldsDivider} />
|
||||
<div style={styles.customFieldsHeader}>Custom Fields</div>
|
||||
{loadingCustomFields ? (
|
||||
<div style={styles.loadingText}>Loading...</div>
|
||||
<SkeletonList count={3} showAvatar={false} />
|
||||
) : (
|
||||
customFields.map((field) => {
|
||||
// Get the value for this field
|
||||
|
||||
Reference in New Issue
Block a user