e1d1c392eb
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>
78 lines
3.2 KiB
React
78 lines
3.2 KiB
React
import { getInitials } from '../utils/api'
|
|
import BrandBadge from './BrandBadge'
|
|
|
|
const ROLE_BADGES = {
|
|
manager: { bg: 'bg-indigo-50', text: 'text-indigo-700', label: 'Manager' },
|
|
approver: { bg: 'bg-emerald-50', text: 'text-emerald-700', label: 'Approver' },
|
|
publisher: { bg: 'bg-blue-50', text: 'text-blue-700', label: 'Publisher' },
|
|
content_creator: { bg: 'bg-amber-50', text: 'text-amber-700', label: 'Content Creator' },
|
|
producer: { bg: 'bg-purple-50', text: 'text-purple-700', label: 'Producer' },
|
|
designer: { bg: 'bg-pink-50', text: 'text-pink-700', label: 'Designer' },
|
|
content_writer: { bg: 'bg-orange-50', text: 'text-orange-700', label: 'Content Writer' },
|
|
social_media_manager: { bg: 'bg-teal-50', text: 'text-teal-700', label: 'Social Media Manager' },
|
|
photographer: { bg: 'bg-cyan-50', text: 'text-cyan-700', label: 'Photographer' },
|
|
videographer: { bg: 'bg-sky-50', text: 'text-sky-700', label: 'Videographer' },
|
|
strategist: { bg: 'bg-rose-50', text: 'text-rose-700', label: 'Strategist' },
|
|
superadmin: { bg: 'bg-red-50', text: 'text-red-700', label: 'Super Admin' },
|
|
contributor: { bg: 'bg-slate-50', text: 'text-slate-700', label: 'Contributor' },
|
|
default: { bg: 'bg-gray-50', text: 'text-text-secondary', label: 'Team Member' },
|
|
}
|
|
|
|
export default function MemberCard({ member, onClick }) {
|
|
const role = ROLE_BADGES[member.team_role || member.role] || ROLE_BADGES.default
|
|
|
|
const avatarColors = [
|
|
'from-indigo-400 to-purple-500',
|
|
'from-pink-400 to-rose-500',
|
|
'from-emerald-400 to-teal-500',
|
|
'from-amber-400 to-orange-500',
|
|
'from-cyan-400 to-blue-500',
|
|
]
|
|
const colorIndex = (member.name?.charCodeAt(0) || 0) % avatarColors.length
|
|
|
|
return (
|
|
<div
|
|
onClick={() => onClick?.(member)}
|
|
className="bg-surface rounded-xl border border-border p-5 card-hover cursor-pointer text-center"
|
|
>
|
|
{/* Avatar */}
|
|
<div className={`w-16 h-16 rounded-full bg-gradient-to-br ${avatarColors[colorIndex]} flex items-center justify-center text-white text-xl font-bold mx-auto mb-3`}>
|
|
{getInitials(member.name)}
|
|
</div>
|
|
|
|
{/* Name */}
|
|
<h4 className="text-base font-semibold text-text-primary">{member.name}</h4>
|
|
|
|
{/* Role badge */}
|
|
<span className={`inline-block text-xs font-medium px-2 py-0.5 rounded-full mt-1 ${role.bg} ${role.text}`}>
|
|
{role.label}
|
|
</span>
|
|
|
|
{/* Email */}
|
|
{member.email && (
|
|
<p className="text-xs text-text-tertiary mt-2">{member.email}</p>
|
|
)}
|
|
|
|
{/* Brands */}
|
|
{member.brands && member.brands.length > 0 && (
|
|
<div className="flex flex-wrap gap-1 justify-center mt-3 pt-3 border-t border-border-light">
|
|
{member.brands.map((brand) => (
|
|
<BrandBadge key={brand} brand={brand} />
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Teams */}
|
|
{member.teams && member.teams.length > 0 && (
|
|
<div className="flex flex-wrap gap-1 justify-center mt-2">
|
|
{member.teams.map((team) => (
|
|
<span key={team.id} className="text-[10px] px-2 py-0.5 rounded-full bg-blue-50 text-blue-600 font-medium">
|
|
{team.name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|