feat: use modals for creation across all pages + fix profile prompt
Deploy / deploy (push) Successful in 11s

- Campaigns: add create modal (name, brand, team, dates, budget)
- PostProduction: add create modal (title, brand, campaign, assignee),
  auto-opens detail panel after creation
- Tasks: add create modal (title, project, priority, assignee),
  auto-opens detail panel after creation
- Fix profileComplete check: use !!user.name instead of !!user.team_role
  in /api/auth/me (was always showing profile prompt since team_role
  is now deprecated in favor of role_id)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-04 16:44:22 +03:00
parent 959bd6066d
commit 643d004dc7
4 changed files with 238 additions and 9 deletions
+71 -2
View File
@@ -49,6 +49,9 @@ export default function Tasks() {
const [showFilters, setShowFilters] = useState(false)
const [selectedIds, setSelectedIds] = useState(new Set())
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false)
const [showCreateModal, setShowCreateModal] = useState(false)
const [createForm, setCreateForm] = useState({ title: '', project_id: '', brand_id: '', priority: 'medium', assigned_to: '' })
const [createSaving, setCreateSaving] = useState(false)
// Assignable users & team
const [assignableUsers, setAssignableUsers] = useState([])
@@ -181,6 +184,32 @@ export default function Tasks() {
}
}
const handleCreateTask = async () => {
setCreateSaving(true)
try {
const data = {
title: createForm.title,
priority: createForm.priority,
status: 'todo',
project_id: createForm.project_id ? Number(createForm.project_id) : null,
brand_id: createForm.brand_id ? Number(createForm.brand_id) : null,
assigned_to: createForm.assigned_to ? Number(createForm.assigned_to) : null,
is_personal: false,
}
const created = await api.post('/tasks', data)
setShowCreateModal(false)
toast.success(t('tasks.created'))
loadTasks()
// Open detail panel for further editing
if (created) setSelectedTask(created)
} catch (err) {
console.error('Create task failed:', err)
toast.error(t('common.saveFailed'))
} finally {
setCreateSaving(false)
}
}
const handleBulkDelete = async () => {
try {
await api.post('/tasks/bulk-delete', { ids: [...selectedIds] })
@@ -355,7 +384,7 @@ export default function Tasks() {
</div>
<button
onClick={() => { setSelectedTask({ title: '', description: '', priority: 'medium', start_date: '', due_date: '', status: 'todo', assigned_to: '', project_id: '' }) }}
onClick={() => { setCreateForm({ title: '', project_id: '', brand_id: '', priority: 'medium', assigned_to: '' }); setShowCreateModal(true) }}
className="flex items-center gap-2 px-4 py-2 bg-brand-primary text-white rounded-lg text-sm font-medium hover:bg-brand-primary-light shadow-sm shrink-0"
>
<Plus className="w-4 h-4" />
@@ -689,6 +718,46 @@ export default function Tasks() {
</>
)}
{/* ─── Create Task Modal ──────────────────── */}
<Modal isOpen={showCreateModal} onClose={() => setShowCreateModal(false)} title={t('tasks.newTask')} size="md">
<div className="space-y-4">
<div>
<label className="block text-xs font-medium text-text-tertiary mb-1">{t('tasks.taskTitle')} *</label>
<input type="text" value={createForm.title} onChange={e => setCreateForm(f => ({ ...f, title: e.target.value }))}
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" autoFocus />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-text-tertiary mb-1">{t('tasks.project')}</label>
<select value={createForm.project_id} onChange={e => setCreateForm(f => ({ ...f, project_id: e.target.value }))}
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
<option value=""></option>
{projects.map(p => <option key={p._id || p.id} value={p._id || p.id}>{p.name}</option>)}
</select>
</div>
<div>
<label className="block text-xs font-medium text-text-tertiary mb-1">{t('tasks.priority')}</label>
<select value={createForm.priority} onChange={e => setCreateForm(f => ({ ...f, priority: e.target.value }))}
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
{Object.entries(PRIORITY_CONFIG).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
</select>
</div>
</div>
<div>
<label className="block text-xs font-medium text-text-tertiary mb-1">{t('tasks.assignedTo')}</label>
<select value={createForm.assigned_to} onChange={e => setCreateForm(f => ({ ...f, assigned_to: e.target.value }))}
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
<option value="">{t('common.unassigned')}</option>
{assignableUsers.map(u => <option key={u._id || u.id} value={u._id || u.id}>{u.name}</option>)}
</select>
</div>
<button onClick={handleCreateTask} disabled={!createForm.title || createSaving}
className={`w-full px-4 py-2.5 bg-brand-primary text-white rounded-lg text-sm font-medium hover:bg-brand-primary-light disabled:opacity-50 disabled:cursor-not-allowed shadow-sm ${createSaving ? 'btn-loading' : ''}`}>
{t('tasks.newTask')}
</button>
</div>
</Modal>
{/* ─── Bulk Delete Confirmation Modal ─────── */}
<Modal
isOpen={showBulkDeleteConfirm}
@@ -702,7 +771,7 @@ export default function Tasks() {
{t('common.bulkDeleteDesc')}
</Modal>
{/* ─── Task Detail Side Panel ──────────────── */}
{/* ─── Task Detail Side Panel (edit only) ─── */}
{selectedTask && (
<TaskDetailPanel
task={selectedTask}