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)
94 lines
3.9 KiB
JavaScript
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>
|
|
)
|
|
}
|