Clean up: Remove src files
This commit is contained in:
parent
f6e4b1de7e
commit
b32b3eaca1
@ -1,202 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AppShell from './components/common/AppShell';
|
||||
import TaskForm from './components/TaskForm/TaskForm';
|
||||
import TaskList, { FilterCounts } from './components/TaskList/TaskList';
|
||||
import { api } from './services/api';
|
||||
import { Task, TaskDraft, TaskFilter, TaskStatus, User } from './types';
|
||||
import './App.css';
|
||||
|
||||
interface Notice {
|
||||
kind: 'info' | 'error';
|
||||
text: string;
|
||||
}
|
||||
|
||||
const errorText = (error: unknown, fallback: string): string =>
|
||||
error instanceof Error && error.message ? error.message : fallback;
|
||||
|
||||
function App() {
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [filter, setFilter] = useState<TaskFilter>('all');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
const [editingTask, setEditingTask] = useState<Task | null>(null);
|
||||
const [pendingTaskId, setPendingTaskId] = useState<number | null>(null);
|
||||
const [notice, setNotice] = useState<Notice | null>(null);
|
||||
|
||||
/** Element that opened the dialog, so focus can go back where the user left it. */
|
||||
const formTriggerRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
const load = useCallback(async (): Promise<void> => {
|
||||
setLoading(true);
|
||||
setLoadError(null);
|
||||
try {
|
||||
const [me, teamMembers, taskList] = await Promise.all([
|
||||
api.getCurrentUser(),
|
||||
api.getUsers(),
|
||||
api.getTasks(),
|
||||
]);
|
||||
setCurrentUser(me);
|
||||
setUsers(teamMembers);
|
||||
setTasks(taskList);
|
||||
} catch (error) {
|
||||
setLoadError(
|
||||
errorText(error, 'TaskFlow could not reach the server. Check your connection and retry.')
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
const counts: FilterCounts = useMemo(
|
||||
() => ({
|
||||
my: tasks.filter((task) => task.assigned_to?.id === currentUser?.id).length,
|
||||
all: tasks.length,
|
||||
blocked: tasks.filter((task) => task.status === 'blocked').length,
|
||||
}),
|
||||
[tasks, currentUser]
|
||||
);
|
||||
|
||||
const visibleTasks = useMemo(() => {
|
||||
if (filter === 'my') {
|
||||
return tasks.filter((task) => task.assigned_to?.id === currentUser?.id);
|
||||
}
|
||||
if (filter === 'blocked') {
|
||||
return tasks.filter((task) => task.status === 'blocked');
|
||||
}
|
||||
return tasks;
|
||||
}, [tasks, filter, currentUser]);
|
||||
|
||||
const openForm = (task: Task | null): void => {
|
||||
formTriggerRef.current = document.activeElement as HTMLElement | null;
|
||||
setEditingTask(task);
|
||||
setNotice(null);
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const closeForm = (): void => {
|
||||
setFormOpen(false);
|
||||
setEditingTask(null);
|
||||
formTriggerRef.current?.focus();
|
||||
};
|
||||
|
||||
/** Rejects on failure so TaskForm can keep the user's input on screen. */
|
||||
const saveTask = async (draft: TaskDraft): Promise<void> => {
|
||||
if (editingTask) {
|
||||
const updated = await api.updateTask(editingTask.id, draft);
|
||||
setTasks((current) => current.map((task) => (task.id === updated.id ? updated : task)));
|
||||
setNotice({ kind: 'info', text: `"${updated.title}" was updated.` });
|
||||
} else {
|
||||
const created = await api.createTask(draft);
|
||||
setTasks((current) => [created, ...current]);
|
||||
setNotice({ kind: 'info', text: `"${created.title}" was created.` });
|
||||
}
|
||||
closeForm();
|
||||
};
|
||||
|
||||
const changeStatus = async (task: Task, status: TaskStatus): Promise<void> => {
|
||||
setPendingTaskId(task.id);
|
||||
setNotice(null);
|
||||
try {
|
||||
const updated = await api.updateTask(task.id, { status });
|
||||
setTasks((current) => current.map((item) => (item.id === updated.id ? updated : item)));
|
||||
setNotice({ kind: 'info', text: `"${updated.title}" is now ${status.replace('_', ' ')}.` });
|
||||
} catch (error) {
|
||||
setNotice({
|
||||
kind: 'error',
|
||||
text: errorText(error, `"${task.title}" could not be updated. It is unchanged, so try again.`),
|
||||
});
|
||||
} finally {
|
||||
setPendingTaskId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTask = async (task: Task): Promise<void> => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const confirmed = window.confirm(`Delete "${task.title}"? This cannot be undone.`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingTaskId(task.id);
|
||||
setNotice(null);
|
||||
try {
|
||||
await api.deleteTask(task.id);
|
||||
setTasks((current) => current.filter((item) => item.id !== task.id));
|
||||
setNotice({ kind: 'info', text: `"${task.title}" was deleted.` });
|
||||
} catch (error) {
|
||||
setNotice({
|
||||
kind: 'error',
|
||||
text: errorText(error, `"${task.title}" could not be deleted. Nothing changed, so try again.`),
|
||||
});
|
||||
} finally {
|
||||
setPendingTaskId(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="app-loading" role="status">
|
||||
<div className="loading-spinner" aria-hidden="true" />
|
||||
<p>Loading your team's tasks...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loadError || !currentUser) {
|
||||
return (
|
||||
<div className="app-error" role="alert">
|
||||
<h1>We could not load your tasks</h1>
|
||||
<p>{loadError ?? 'Your account details are unavailable right now.'}</p>
|
||||
<button type="button" className="btn btn-primary retry-button" onClick={load}>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppShell currentUser={currentUser} users={users}>
|
||||
<div className="app-notice-region" aria-live="polite">
|
||||
{notice && (
|
||||
<div className={`app-notice ${notice.kind}`}>
|
||||
<span>{notice.text}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="notice-dismiss"
|
||||
onClick={() => setNotice(null)}
|
||||
aria-label="Dismiss message"
|
||||
>
|
||||
<span aria-hidden="true">✕</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TaskList
|
||||
tasks={visibleTasks}
|
||||
totalTaskCount={tasks.length}
|
||||
counts={counts}
|
||||
currentFilter={filter}
|
||||
pendingTaskId={pendingTaskId}
|
||||
onFilterChange={setFilter}
|
||||
onCreateTask={() => openForm(null)}
|
||||
onEditTask={openForm}
|
||||
onDeleteTask={deleteTask}
|
||||
onStatusChange={changeStatus}
|
||||
/>
|
||||
|
||||
{formOpen && (
|
||||
<TaskForm users={users} task={editingTask} onCancel={closeForm} onSubmit={saveTask} />
|
||||
)}
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Loading…
Reference in New Issue
Block a user