Files
marketing-app/client/src/components/ArtefactVersionTimeline.jsx
T
fahed e1d1c392eb feat: comprehensive UI overhaul + budget allocation redesign
Audit & Quality:
- RTL: replaced 121 LTR-only utilities (text-left, pl-, left-) with logical properties
- A11y: focus traps + ARIA on Modal/SlidePanel/TabbedModal, clickable divs→buttons
- Theming: bg-white→bg-surface (170 instances), text-gray→semantic tokens
- Performance: useMemo on filters, loading="lazy" on 24 images
- CSS: prefers-reduced-motion, removed dead animations

Component Splits:
- PostDetailPanel: 1332→623 lines + 4 sub-components
- ArtefactDetailPanel: 972→590 lines + 1 sub-component

Brand Identity — Rawaj (رواج):
- New name, DM Sans font, deep teal palette (#0d9488)
- Custom SVG logo, forest-tinted dark mode
- All emails branded with app name in subject line

Design Refinement:
- Dashboard: merged posts+deadlines into tabbed ActivityFeed, inline stats
- Quieter: removed card lift, brand glow, gradient text, mesh backgrounds
- CampaignDetail: prominent budget card, compact team avatars, Lucide icons
- Consistent page titles via Header.jsx, standardized section headers
- Finance page fully i18n'd (20+ hardcoded strings replaced)

Budget Allocation Redesign:
- Single source of truth: BudgetEntries (Campaign.budget deprecated)
- Validation at all levels: main→campaign→track, expenses blocked if insufficient
- Budget request workflow with CEO approval via public link
- BudgetRequests table, CRUD routes, public approval page
- Budget mutex for race condition prevention
- Idempotent migration for existing campaign budgets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 15:36:19 +03:00

98 lines
3.5 KiB
React

import { Check, Clock, User } from 'lucide-react'
export default function ArtefactVersionTimeline({ versions, activeVersionId, onSelectVersion, artefactType }) {
if (!versions || versions.length === 0) {
return (
<div className="text-center py-4 text-sm text-text-tertiary">
No versions found
</div>
)
}
return (
<div className="space-y-2">
{versions.map((version, idx) => {
const isActive = version.Id === activeVersionId
const isLatest = idx === versions.length - 1
return (
<button
key={version.Id}
onClick={() => onSelectVersion(version)}
className={`w-full text-start p-3 rounded-lg border transition-colors ${
isActive
? 'border-brand-primary bg-brand-primary/5'
: 'border-border hover:border-brand-primary/30 bg-surface hover:shadow-sm'
}`}
>
<div className="flex items-start gap-3">
{/* Version indicator */}
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold ${
isActive
? 'bg-brand-primary text-white'
: 'bg-surface-secondary text-text-secondary'
}`}>
v{version.version_number}
</div>
{/* Version details */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className={`text-sm font-medium ${isActive ? 'text-brand-primary' : 'text-text-primary'}`}>
Version {version.version_number}
</span>
{isLatest && (
<span className="text-xs px-2 py-0.5 bg-emerald-100 text-emerald-700 rounded-full font-medium">
Latest
</span>
)}
</div>
{version.notes && (
<p className="text-xs text-text-secondary line-clamp-2 mb-2">
{version.notes}
</p>
)}
<div className="flex items-center gap-3 text-xs text-text-tertiary">
{version.creator_name && (
<div className="flex items-center gap-1">
<User className="w-3 h-3" />
<span>{version.creator_name}</span>
</div>
)}
{version.created_at && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{new Date(version.created_at).toLocaleDateString()}</span>
</div>
)}
</div>
</div>
{/* Active indicator */}
{isActive && (
<div className="flex-shrink-0">
<Check className="w-5 h-5 text-brand-primary" />
</div>
)}
</div>
{/* Thumbnail for image artefacts */}
{artefactType === 'design' && version.thumbnail && (
<div className="mt-2 ms-11">
<img
src={version.thumbnail}
alt={`Version ${version.version_number}`}
className="w-full h-20 object-cover rounded border border-border"
loading="lazy"
/>
</div>
)}
</button>
)
})}
</div>
)
}