feat(todos): composant TodoForm (rapide mobile / étendu laptop)
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import type { TodoCreate } from '../../api/todos'
|
||||||
|
|
||||||
|
const DOMAINS = [
|
||||||
|
'informatique', 'diy', 'electronique', 'domotique',
|
||||||
|
'bricolage', 'jardin', 'cuisine', 'voyage', 'animaux',
|
||||||
|
]
|
||||||
|
|
||||||
|
interface TodoFormProps {
|
||||||
|
onSubmit: (data: TodoCreate) => Promise<void>
|
||||||
|
onCancel: () => void
|
||||||
|
extended?: boolean // true = tous les champs (vue laptop)
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputStyle: React.CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
background: 'var(--bg-4)',
|
||||||
|
border: '1px solid var(--bg-5)',
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: '10px 12px',
|
||||||
|
color: 'var(--ink-1)',
|
||||||
|
fontFamily: 'var(--font-ui)',
|
||||||
|
fontSize: 14,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TodoForm({ onSubmit, onCancel, extended = false }: TodoFormProps) {
|
||||||
|
const [title, setTitle] = useState('')
|
||||||
|
const [domain, setDomain] = useState('')
|
||||||
|
const [priority, setPriority] = useState('medium')
|
||||||
|
const [dueDate, setDueDate] = useState('')
|
||||||
|
const [body, setBody] = useState('')
|
||||||
|
const [url, setUrl] = useState('')
|
||||||
|
const [tags, setTags] = useState('')
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!title.trim()) return
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await onSubmit({
|
||||||
|
title: title.trim(),
|
||||||
|
domain: domain || undefined,
|
||||||
|
priority,
|
||||||
|
due_date: dueDate ? new Date(dueDate).toISOString() : undefined,
|
||||||
|
body: body.trim() || undefined,
|
||||||
|
url: url.trim() || undefined,
|
||||||
|
tags: tags ? tags.split(',').map(t => t.trim()).filter(Boolean) : [],
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="Titre de la tâche *"
|
||||||
|
value={title}
|
||||||
|
onChange={e => setTitle(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
|
||||||
|
<select style={inputStyle} value={domain} onChange={e => setDomain(e.target.value)}>
|
||||||
|
<option value="">Domaine</option>
|
||||||
|
{DOMAINS.map(d => <option key={d} value={d}>{d}</option>)}
|
||||||
|
</select>
|
||||||
|
<select style={inputStyle} value={priority} onChange={e => setPriority(e.target.value)}>
|
||||||
|
<option value="low">Priorité basse</option>
|
||||||
|
<option value="medium">Priorité moyenne</option>
|
||||||
|
<option value="high">Priorité haute</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
type="date"
|
||||||
|
value={dueDate}
|
||||||
|
onChange={e => setDueDate(e.target.value)}
|
||||||
|
placeholder="Date objectif"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{extended && (
|
||||||
|
<>
|
||||||
|
<textarea
|
||||||
|
style={{ ...inputStyle, minHeight: 80, resize: 'vertical' }}
|
||||||
|
placeholder="Description"
|
||||||
|
value={body}
|
||||||
|
onChange={e => setBody(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="URL (lien externe optionnel)"
|
||||||
|
value={url}
|
||||||
|
onChange={e => setUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
style={inputStyle}
|
||||||
|
placeholder="Tags (séparés par virgule)"
|
||||||
|
value={tags}
|
||||||
|
onChange={e => setTags(e.target.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
style={{
|
||||||
|
padding: '10px 16px',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: '1px solid var(--bg-5)',
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'var(--font-ui)',
|
||||||
|
minHeight: 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
style={{
|
||||||
|
padding: '10px 20px',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: 'none',
|
||||||
|
background: 'var(--accent)',
|
||||||
|
color: '#1d2021',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'var(--font-ui)',
|
||||||
|
fontWeight: 600,
|
||||||
|
minHeight: 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '…' : 'Créer'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user