Files
marketing-app/client/src/components/TaskCard.jsx
fahed 35d84b6bff Marketing Hub: RBAC, i18n (AR/EN), tasks overhaul, team/user merge, tutorial
Features:
- Full RBAC with 3 roles (superadmin/manager/contributor)
- Ownership tracking on posts, tasks, campaigns, projects
- Task system: assign to anyone, filter combobox, visibility scoping
- Team members merged into users table (single source of truth)
- Post thumbnails on kanban cards from attachments
- Publication link validation before publishing
- Interactive onboarding tutorial with Settings restart
- Full Arabic/English i18n with RTL layout support
- Language toggle in sidebar, IBM Plex Sans Arabic font
- Brand-based visibility filtering for non-superadmins
- Manager can only create contributors
- Profile completion flow for new users
- Cookie-based sessions (express-session + SQLite)
2026-02-08 20:46:58 +03:00

94 lines
3.9 KiB
JavaScript

import { format } from 'date-fns'
import { ArrowRight, Clock, User, UserCheck } from 'lucide-react'
import { PRIORITY_CONFIG } from '../utils/api'
import { useAuth } from '../contexts/AuthContext'
import { useLanguage } from '../i18n/LanguageContext'
export default function TaskCard({ task, onMove, showProject = true }) {
const { t } = useLanguage()
const { user: authUser } = useAuth()
const priority = PRIORITY_CONFIG[task.priority] || PRIORITY_CONFIG.medium
const projectName = typeof task.project === 'object' ? task.project?.name : task.projectName
const nextStatus = {
todo: 'in_progress',
in_progress: 'done',
}
const nextLabel = {
todo: t('tasks.start'),
in_progress: t('tasks.complete'),
}
const dueDate = task.due_date || task.dueDate
const isOverdue = dueDate && new Date(dueDate) < new Date() && task.status !== 'done'
const creatorName = task.creator_user_name || task.creatorUserName
// Determine if this task was assigned by someone else
const createdByUserId = task.created_by_user_id || task.createdByUserId
const isExternallyAssigned = authUser && createdByUserId && createdByUserId !== authUser.id
const assignedName = task.assigned_name || task.assignedName
return (
<div className={`bg-white rounded-lg border border-border p-3 card-hover group ${isExternallyAssigned ? 'border-l-[3px] border-l-blue-400' : ''}`}>
<div className="flex items-start gap-2.5">
{/* Priority dot */}
<div className={`w-2.5 h-2.5 rounded-full ${priority.color} mt-1.5 shrink-0`} title={priority.label} />
<div className="flex-1 min-w-0">
<h5 className={`text-sm font-medium leading-snug ${task.status === 'done' ? 'text-text-tertiary line-through' : 'text-text-primary'}`}>
{task.title}
</h5>
{/* Assigned by label for externally-assigned tasks */}
{isExternallyAssigned && creatorName && (
<div className="flex items-center gap-1 mt-1">
<UserCheck className="w-3 h-3 text-blue-400" />
<span className="text-[10px] text-blue-500 font-medium">{t('tasks.from')} {creatorName}</span>
</div>
)}
{/* Assigned to label for tasks you delegated */}
{!isExternallyAssigned && assignedName && (
<div className="flex items-center gap-1 mt-1">
<User className="w-3 h-3 text-emerald-400" />
<span className="text-[10px] text-emerald-500 font-medium">{t('tasks.assignedTo')} {assignedName}</span>
</div>
)}
<div className="flex items-center gap-2 mt-1.5 flex-wrap">
{showProject && projectName && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-surface-tertiary text-text-tertiary">
{projectName}
</span>
)}
{dueDate && (
<span className={`text-[10px] flex items-center gap-1 ${isOverdue ? 'text-red-500 font-medium' : 'text-text-tertiary'}`}>
<Clock className="w-3 h-3" />
{format(new Date(dueDate), 'MMM d')}
</span>
)}
{!isExternallyAssigned && creatorName && (
<span className="text-[10px] flex items-center gap-1 text-text-tertiary">
<User className="w-3 h-3" />
{creatorName}
</span>
)}
</div>
</div>
</div>
{/* Quick action */}
{onMove && nextStatus[task.status] && (
<div className="mt-2 pt-2 border-t border-border-light opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => onMove(task._id || task.id, nextStatus[task.status])}
className="text-[11px] text-brand-primary hover:text-brand-primary-light font-medium flex items-center gap-1"
>
{nextLabel[task.status]} <ArrowRight className="w-3 h-3" />
</button>
</div>
)}
</div>
)
}