Clean up: Remove component files

This commit is contained in:
Samuel James 2026-09-05 15:28:29 +00:00
parent c6853c6f0e
commit 78faf60591

View File

@ -1,297 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import {
DESCRIPTION_MAX_LENGTH,
PRIORITY_LABELS,
STATUS_LABELS,
TASK_PRIORITIES,
TASK_STATUSES,
Task,
TaskDraft,
TaskPriority,
TaskStatus,
TITLE_MAX_LENGTH,
User,
} from '../../types';
import './TaskForm.css';
interface TaskFormProps {
users: User[];
/** Task being edited, or null to create a new one. */
task: Task | null;
onCancel: () => void;
/** Rejects with a user-safe message; the form stays open so nothing is lost. */
onSubmit: (draft: TaskDraft) => Promise<void>;
}
interface FormValues {
title: string;
description: string;
assignee_id: string;
status: TaskStatus;
priority: TaskPriority;
due_date: string;
}
const FOCUSABLE =
'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [href]';
const TaskForm: React.FC<TaskFormProps> = ({ users, task, onCancel, onSubmit }) => {
const dialogRef = useRef<HTMLDivElement>(null);
const [submitError, setSubmitError] = useState<string | null>(null);
const {
register,
handleSubmit,
setFocus,
formState: { errors, isSubmitting },
} = useForm<FormValues>({
defaultValues: {
title: task?.title ?? '',
description: task?.description ?? '',
assignee_id: task?.assigned_to ? String(task.assigned_to.id) : '',
status: task?.status ?? 'open',
priority: task?.priority ?? 'medium',
due_date: task?.due_date ?? '',
},
});
useEffect(() => {
setFocus('title');
}, [setFocus]);
const submit = async (values: FormValues): Promise<void> => {
setSubmitError(null);
try {
await onSubmit({
title: values.title,
description: values.description,
assignee_id: Number(values.assignee_id),
status: values.status,
priority: values.priority,
due_date: values.due_date,
});
} catch (error) {
setSubmitError(
error instanceof Error
? error.message
: 'The task could not be saved. Your details are still here, so you can try again.'
);
}
};
/** Escape closes; Tab stays inside the dialog. */
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
if (event.key === 'Escape') {
event.stopPropagation();
onCancel();
return;
}
if (event.key !== 'Tab' || !dialogRef.current) {
return;
}
const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(FOCUSABLE));
if (focusable.length === 0) {
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
};
const titleId = 'task-form-title';
return (
<div className="task-form-overlay">
<div
className="task-form-container"
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
ref={dialogRef}
onKeyDown={handleKeyDown}
>
<form className="task-form" onSubmit={handleSubmit(submit)} noValidate>
<div className="form-header">
<h2 id={titleId}>{task ? 'Edit Task' : 'Create New Task'}</h2>
<button
type="button"
className="close-btn"
onClick={onCancel}
aria-label="Close without saving"
>
<span className="close-text">Close</span>
<span aria-hidden="true">&#10005;</span>
</button>
</div>
{submitError && (
<p className="form-error-banner" role="alert">
{submitError}
</p>
)}
<div className="form-group">
<label className="form-label required" htmlFor="task-title">
Task Title
</label>
<input
id="task-title"
type="text"
className="form-input"
placeholder="e.g., Update client onboarding documentation"
aria-required="true"
aria-invalid={errors.title ? 'true' : 'false'}
aria-describedby={errors.title ? 'task-title-error' : undefined}
{...register('title', {
required: 'Enter a task title so your team knows what to do.',
maxLength: {
value: TITLE_MAX_LENGTH,
message: `Keep the title under ${TITLE_MAX_LENGTH} characters.`,
},
})}
/>
{errors.title ? (
<p className="field-error" id="task-title-error" role="alert">
{errors.title.message}
</p>
) : (
<p className="form-hint">
<span className="required-marker" aria-hidden="true">
*
</span>{' '}
Required field
</p>
)}
</div>
<div className="form-group">
<label className="form-label" htmlFor="task-description">
Description
</label>
<textarea
id="task-description"
className="form-textarea"
rows={3}
placeholder="Enter detailed task description..."
aria-invalid={errors.description ? 'true' : 'false'}
aria-describedby={errors.description ? 'task-description-error' : undefined}
{...register('description', {
maxLength: {
value: DESCRIPTION_MAX_LENGTH,
message: `Keep the description under ${DESCRIPTION_MAX_LENGTH} characters.`,
},
})}
/>
{errors.description && (
<p className="field-error" id="task-description-error" role="alert">
{errors.description.message}
</p>
)}
</div>
<div className="form-group">
<label className="form-label required" htmlFor="task-assignee">
Assignee
</label>
<select
id="task-assignee"
className="form-select"
aria-required="true"
aria-invalid={errors.assignee_id ? 'true' : 'false'}
aria-describedby={errors.assignee_id ? 'task-assignee-error' : undefined}
{...register('assignee_id', {
required: 'Choose the team member who owns this task.',
})}
>
<option value="">Select assignee...</option>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.full_name}
</option>
))}
</select>
{errors.assignee_id && (
<p className="field-error" id="task-assignee-error" role="alert">
{errors.assignee_id.message}
</p>
)}
</div>
<fieldset className="form-group status-fieldset">
<legend className="form-label required">Status</legend>
<div className="status-buttons">
{TASK_STATUSES.map((status) => (
<label key={status} className="status-option">
<input type="radio" value={status} {...register('status')} />
<span className={`status-pill ${status}`}>{STATUS_LABELS[status]}</span>
</label>
))}
</div>
</fieldset>
<div className="form-row">
<div className="form-group">
<label className="form-label required" htmlFor="task-due-date">
Due Date
</label>
<input
id="task-due-date"
type="date"
className="form-input"
aria-required="true"
aria-invalid={errors.due_date ? 'true' : 'false'}
aria-describedby={errors.due_date ? 'task-due-date-error' : undefined}
{...register('due_date', { required: 'Pick a due date for this task.' })}
/>
{errors.due_date && (
<p className="field-error" id="task-due-date-error" role="alert">
{errors.due_date.message}
</p>
)}
</div>
<div className="form-group">
<label className="form-label required" htmlFor="task-priority">
Priority
</label>
<select id="task-priority" className="form-select" {...register('priority')}>
{TASK_PRIORITIES.map((priority) => (
<option key={priority} value={priority}>
{PRIORITY_LABELS[priority]}
</option>
))}
</select>
</div>
</div>
<div className="form-actions">
<button type="submit" className="btn btn-primary" disabled={isSubmitting}>
{isSubmitting ? 'Saving...' : 'Save Task'}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={onCancel}
disabled={isSubmitting}
>
Cancel
</button>
</div>
</form>
</div>
</div>
);
};
export default TaskForm;