Add files via upload

This commit is contained in:
jasonMPM
2026-03-05 12:13:22 -06:00
committed by GitHub
parent bfa3887e61
commit 20e71ee7f9
40 changed files with 1352 additions and 368 deletions

View File

@@ -0,0 +1,88 @@
import { useState, useEffect } from 'react'
import Modal from '../UI/Modal'
import Button from '../UI/Button'
import { createDeliverable, updateDeliverable, deleteDeliverable } from '../../api/deliverables'
import useProjectStore from '../../store/useProjectStore'
import { STATUS_OPTIONS } from '../../utils/statusHelpers'
export default function DeliverableModal({ isOpen, onClose, deliverable, projectId, defaultDate }) {
const { addDeliverable, updateDeliverable: storeUpdate, removeDeliverable, projects } = useProjectStore()
const [title, setTitle] = useState('')
const [dueDate, setDueDate] = useState('')
const [status, setStatus] = useState('upcoming')
const [pid, setPid] = useState('')
const [saving, setSaving] = useState(false)
const isEditing = !!deliverable
useEffect(() => {
if (deliverable) {
setTitle(deliverable.title || ''); setDueDate(deliverable.due_date?.substring(0,10) || '')
setStatus(deliverable.status || 'upcoming'); setPid(deliverable.project_id || '')
} else {
setTitle(''); setDueDate(defaultDate || ''); setStatus('upcoming'); setPid(projectId || '')
}
}, [deliverable, isOpen, projectId, defaultDate])
const handleDelete = async () => {
if (!window.confirm('Delete this deliverable?')) return
await deleteDeliverable(deliverable.id); removeDeliverable(deliverable.id); onClose()
}
const handleSubmit = async () => {
if (!title.trim() || !dueDate || !pid) return
setSaving(true)
try {
if (isEditing) {
storeUpdate(await updateDeliverable(deliverable.id, { title, due_date: dueDate, status }))
} else {
addDeliverable(await createDeliverable({ title, due_date: dueDate, status, project_id: Number(pid) }))
}
onClose()
} finally { setSaving(false) }
}
return (
<Modal isOpen={isOpen} onClose={onClose} title={isEditing ? 'Edit Deliverable' : 'Add Deliverable'}>
<div className="space-y-4">
{!isEditing && (
<div>
<label className="block text-xs text-text-muted mb-1 font-medium">Project *</label>
<select className="w-full bg-surface border border-surface-border rounded-lg px-3 py-2 text-sm text-text-primary focus:outline-none focus:border-gold"
value={pid} onChange={e => setPid(e.target.value)}>
<option value="">Select a project...</option>
{projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>
</div>
)}
<div>
<label className="block text-xs text-text-muted mb-1 font-medium">Title *</label>
<input className="w-full bg-surface border border-surface-border rounded-lg px-3 py-2 text-sm text-text-primary focus:outline-none focus:border-gold transition-colors"
value={title} onChange={e => setTitle(e.target.value)} placeholder="Deliverable title..." />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-text-muted mb-1 font-medium">Due Date *</label>
<input type="date" className="w-full bg-surface border border-surface-border rounded-lg px-3 py-2 text-sm text-text-primary focus:outline-none focus:border-gold transition-colors"
value={dueDate} onChange={e => setDueDate(e.target.value)} />
</div>
<div>
<label className="block text-xs text-text-muted mb-1 font-medium">Status</label>
<select className="w-full bg-surface border border-surface-border rounded-lg px-3 py-2 text-sm text-text-primary focus:outline-none focus:border-gold"
value={status} onChange={e => setStatus(e.target.value)}>
{STATUS_OPTIONS.map(s => <option key={s.value} value={s.value}>{s.label}</option>)}
</select>
</div>
</div>
<div className="flex justify-between pt-2 border-t border-surface-border">
{isEditing ? <Button variant="danger" onClick={handleDelete}>Delete</Button> : <div />}
<div className="flex gap-2">
<Button variant="secondary" onClick={onClose}>Cancel</Button>
<Button onClick={handleSubmit} disabled={saving || !title.trim() || !dueDate || !pid}>
{saving ? 'Saving...' : isEditing ? 'Save Changes' : 'Add Deliverable'}
</Button>
</div>
</div>
</div>
</Modal>
)
}