adding brand management

This commit is contained in:
fahed
2026-02-10 21:03:36 +03:00
parent 334727b232
commit f3e6fc848d
15 changed files with 568 additions and 864 deletions

View File

@@ -4,6 +4,7 @@ import { ArrowLeft, Plus, Edit2, Trash2, DollarSign, Eye, MousePointer, Target,
import { format } from 'date-fns'
import { AppContext } from '../App'
import { useAuth } from '../contexts/AuthContext'
import { useLanguage } from '../i18n/LanguageContext'
import { api, PLATFORMS, getInitials } from '../utils/api'
import PlatformIcon, { PlatformIcons } from '../components/PlatformIcon'
import StatusBadge from '../components/StatusBadge'
@@ -43,7 +44,8 @@ function MetricBox({ label, value, icon: Icon, color = 'text-text-primary' }) {
export default function CampaignDetail() {
const { id } = useParams()
const navigate = useNavigate()
const { brands } = useContext(AppContext)
const { brands, getBrandName } = useContext(AppContext)
const { lang } = useLanguage()
const { permissions, user } = useAuth()
const isSuperadmin = user?.role === 'superadmin'
const [campaign, setCampaign] = useState(null)
@@ -69,6 +71,7 @@ export default function CampaignDetail() {
const [showDiscussion, setShowDiscussion] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const [editForm, setEditForm] = useState({})
const [showDeleteCampaignConfirm, setShowDeleteCampaignConfirm] = useState(false)
const isCreator = campaign?.createdByUserId === user?.id || campaign?.created_by_user_id === user?.id
const canManage = isSuperadmin || (permissions?.canEditCampaigns && isCreator)
@@ -215,6 +218,8 @@ export default function CampaignDetail() {
goals: campaign.goals || '',
platforms: campaign.platforms || [],
notes: campaign.notes || '',
brand_id: campaign.brand_id || '',
budget: campaign.budget || '',
})
setShowEditModal(true)
}
@@ -230,6 +235,8 @@ export default function CampaignDetail() {
goals: editForm.goals,
platforms: editForm.platforms,
notes: editForm.notes,
brand_id: editForm.brand_id || null,
budget: editForm.budget ? Number(editForm.budget) : null,
})
setShowEditModal(false)
loadAll()
@@ -284,7 +291,7 @@ export default function CampaignDetail() {
<div className="flex items-center gap-3 mb-1">
<h1 className="text-xl font-bold text-text-primary">{campaign.name}</h1>
<StatusBadge status={campaign.status} />
{campaign.brand_name && <BrandBadge brand={campaign.brand_name} />}
{(campaign.brand_id || campaign.brand_name) && <BrandBadge brand={getBrandName(campaign.brand_id) || campaign.brand_name} />}
</div>
{campaign.description && <p className="text-sm text-text-secondary">{campaign.description}</p>}
<div className="flex items-center gap-3 mt-2 text-xs text-text-tertiary">
@@ -852,6 +859,19 @@ export default function CampaignDetail() {
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary"
/>
</div>
<div>
<label className="block text-sm font-medium text-text-primary mb-1">Brand</label>
<select
value={editForm.brand_id || ''}
onChange={e => setEditForm(f => ({ ...f, brand_id: e.target.value }))}
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary"
>
<option value="">No brand</option>
{brands.map(b => (
<option key={b.id} value={b.id}>{lang === 'ar' && b.name_ar ? b.name_ar : b.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-text-primary mb-1">Description</label>
<textarea
@@ -861,7 +881,7 @@ export default function CampaignDetail() {
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary resize-none"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-text-primary mb-1">Status</label>
<select
@@ -885,6 +905,17 @@ export default function CampaignDetail() {
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary"
/>
</div>
<div>
<label className="block text-sm font-medium text-text-primary mb-1">Budget (SAR)</label>
<input
type="number"
value={editForm.budget || ''}
onChange={e => setEditForm(f => ({ ...f, budget: e.target.value }))}
min="0"
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary"
placeholder="0"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
@@ -949,6 +980,14 @@ export default function CampaignDetail() {
/>
</div>
<div className="flex items-center justify-end gap-3 pt-4 border-t border-border">
{permissions?.canDeleteCampaigns && (
<button
onClick={() => setShowDeleteCampaignConfirm(true)}
className="px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-lg mr-auto"
>
Delete Campaign
</button>
)}
<button
onClick={() => setShowEditModal(false)}
className="px-4 py-2 text-sm font-medium text-text-secondary hover:bg-surface-tertiary rounded-lg"
@@ -966,6 +1005,28 @@ export default function CampaignDetail() {
</div>
</Modal>
{/* Delete Campaign Confirmation */}
<Modal
isOpen={showDeleteCampaignConfirm}
onClose={() => setShowDeleteCampaignConfirm(false)}
title="Delete Campaign?"
isConfirm
danger
confirmText="Delete Campaign"
onConfirm={async () => {
try {
await api.delete(`/campaigns/${id}`)
setShowDeleteCampaignConfirm(false)
setShowEditModal(false)
navigate('/campaigns')
} catch (err) {
console.error('Failed to delete campaign:', err)
}
}}
>
Are you sure you want to delete this campaign? All tracks and linked data will be permanently removed. This action cannot be undone.
</Modal>
{/* Post Detail Modal */}
<Modal
isOpen={!!selectedPost}