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>
This commit is contained in:
@@ -145,7 +145,7 @@ export default function Campaigns() {
|
||||
<select
|
||||
value={filters.brand}
|
||||
onChange={e => setFilters(f => ({ ...f, brand: e.target.value }))}
|
||||
className="text-sm border border-border rounded-lg px-3 py-2 bg-white text-text-secondary focus:outline-none"
|
||||
className="text-sm border border-border rounded-lg px-3 py-2 bg-surface text-text-secondary focus:outline-none"
|
||||
>
|
||||
<option value="">All Brands</option>
|
||||
{brands.map(b => <option key={b.id || b._id} value={b.id || b._id}>{lang === 'ar' && b.name_ar ? b.name_ar : b.name}</option>)}
|
||||
@@ -154,7 +154,7 @@ export default function Campaigns() {
|
||||
<select
|
||||
value={filters.status}
|
||||
onChange={e => setFilters(f => ({ ...f, status: e.target.value }))}
|
||||
className="text-sm border border-border rounded-lg px-3 py-2 bg-white text-text-secondary focus:outline-none"
|
||||
className="text-sm border border-border rounded-lg px-3 py-2 bg-surface text-text-secondary focus:outline-none"
|
||||
>
|
||||
<option value="">All Statuses</option>
|
||||
<option value="planning">Planning</option>
|
||||
@@ -167,7 +167,7 @@ export default function Campaigns() {
|
||||
{permissions?.canCreateCampaigns && (
|
||||
<button
|
||||
onClick={openNew}
|
||||
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 ml-auto"
|
||||
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 ms-auto"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
New Campaign
|
||||
@@ -178,7 +178,7 @@ export default function Campaigns() {
|
||||
{/* Summary Cards */}
|
||||
{(totalBudget > 0 || totalSpent > 0) && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3 stagger-children">
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<DollarSign className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Budget</span>
|
||||
@@ -186,7 +186,7 @@ export default function Campaigns() {
|
||||
<div className="text-lg font-bold text-text-primary">{totalBudget.toLocaleString()}</div>
|
||||
<div className="text-[10px] text-text-tertiary">{currencySymbol} total</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<TrendingUp className="w-4 h-4 text-amber-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Spent</span>
|
||||
@@ -194,28 +194,28 @@ export default function Campaigns() {
|
||||
<div className="text-lg font-bold text-text-primary">{totalSpent.toLocaleString()}</div>
|
||||
<div className="text-[10px] text-text-tertiary">{currencySymbol} spent</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Eye className="w-4 h-4 text-purple-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Impressions</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-text-primary">{totalImpressions.toLocaleString()}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MousePointer className="w-4 h-4 text-green-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Clicks</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-text-primary">{totalClicks.toLocaleString()}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Target className="w-4 h-4 text-red-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Conversions</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-text-primary">{totalConversions.toLocaleString()}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-border p-4">
|
||||
<div className="bg-surface rounded-xl border border-border p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<BarChart3 className="w-4 h-4 text-emerald-500" />
|
||||
<span className="text-[10px] uppercase tracking-wider text-text-tertiary font-medium">Revenue</span>
|
||||
@@ -264,7 +264,7 @@ export default function Campaigns() {
|
||||
/>
|
||||
|
||||
{/* Campaign list */}
|
||||
<div className="bg-white rounded-xl border border-border overflow-hidden">
|
||||
<div className="bg-surface rounded-xl border border-border overflow-hidden">
|
||||
<div className="px-5 py-4 border-b border-border">
|
||||
<h3 className="font-semibold text-text-primary">All Campaigns</h3>
|
||||
</div>
|
||||
@@ -308,7 +308,7 @@ export default function Campaigns() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<div className="text-end shrink-0">
|
||||
<StatusBadge status={campaign.status} size="xs" />
|
||||
<div className="text-xs text-text-tertiary mt-1">
|
||||
{campaign.startDate && campaign.endDate ? (
|
||||
|
||||
Reference in New Issue
Block a user