generated from ludops/ludops-skeleton
139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import './App.css'
|
|
import Timeline from './components/Timeline'
|
|
import TodaysTasks from './components/TodaysTasks'
|
|
import CreateTaskForm from './components/CreateTaskForm'
|
|
import type { Task, NewTaskInput } from './types'
|
|
import { isTaskDueToday } from './utils/taskUtils'
|
|
|
|
const APP_NAME = 'TODO'
|
|
|
|
type ApiStatus = 'loading' | 'ok' | 'error'
|
|
|
|
function App() {
|
|
const [apiStatus, setApiStatus] = useState<ApiStatus>('loading')
|
|
const [tasks, setTasks] = useState<Task[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
// Fetch tasks
|
|
const fetchTasks = async () => {
|
|
try {
|
|
const response = await fetch('/api/tasks')
|
|
if (!response.ok) throw new Error('Failed to fetch tasks')
|
|
const data = await response.json()
|
|
setTasks(data)
|
|
} catch (error) {
|
|
console.error('Error fetching tasks:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
// Check API status
|
|
fetch('/api/status')
|
|
.then((r) => (r.ok ? r.json() : Promise.reject()))
|
|
.then(() => setApiStatus('ok'))
|
|
.catch(() => setApiStatus('error'))
|
|
|
|
// Fetch tasks
|
|
fetchTasks()
|
|
}, [])
|
|
|
|
const handleCreateTask = async (taskInput: NewTaskInput) => {
|
|
setError(null)
|
|
try {
|
|
const response = await fetch('/api/tasks', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(taskInput),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json()
|
|
setError(errorData.error || 'Failed to create task')
|
|
return
|
|
}
|
|
|
|
await fetchTasks()
|
|
} catch (error) {
|
|
console.error('Error creating task:', error)
|
|
setError('Failed to create task')
|
|
}
|
|
}
|
|
|
|
const handleCompleteTask = async (taskId: number) => {
|
|
setError(null)
|
|
try {
|
|
const response = await fetch(`/api/tasks/${taskId}/complete`, {
|
|
method: 'PATCH',
|
|
})
|
|
|
|
if (!response.ok) {
|
|
setError('Failed to complete task')
|
|
return
|
|
}
|
|
await fetchTasks()
|
|
} catch (error) {
|
|
console.error('Error completing task:', error)
|
|
setError('Failed to complete task')
|
|
}
|
|
}
|
|
|
|
const handleRenewTask = async (taskId: number) => {
|
|
setError(null)
|
|
try {
|
|
const response = await fetch(`/api/tasks/${taskId}/renew`, {
|
|
method: 'PATCH',
|
|
})
|
|
|
|
if (!response.ok) {
|
|
setError('Failed to renew task')
|
|
return
|
|
}
|
|
await fetchTasks()
|
|
} catch (error) {
|
|
console.error('Error renewing task:', error)
|
|
setError('Failed to renew task')
|
|
}
|
|
}
|
|
|
|
const todaysTasks = tasks.filter(isTaskDueToday)
|
|
|
|
return (
|
|
<main>
|
|
<h1>{APP_NAME}</h1>
|
|
|
|
<div className={`status status--${apiStatus}`}>
|
|
{apiStatus === 'loading' && 'Connecting to API…'}
|
|
{apiStatus === 'ok' && '✓ API & database connected'}
|
|
{apiStatus === 'error' && '⚠️ Could not reach API'}
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="error-message">
|
|
{error}
|
|
<button onClick={() => setError(null)} className="error-dismiss">✕</button>
|
|
</div>
|
|
)}
|
|
|
|
{loading ? (
|
|
<p>Loading tasks...</p>
|
|
) : (
|
|
<>
|
|
<TodaysTasks
|
|
tasks={todaysTasks}
|
|
onComplete={handleCompleteTask}
|
|
onRenew={handleRenewTask}
|
|
/>
|
|
<CreateTaskForm onCreateTask={handleCreateTask} />
|
|
<Timeline tasks={tasks} />
|
|
</>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default App
|