Files
helix-engage/src/components/campaigns/campaign-edit-slideout.tsx
saridsa2 dbd8391f2c fix: UUID type mismatch, slot conflict, appt/enquiry tabs, dialler in header
- Changed $id: ID! to $id: UUID! in all update mutations (4 files)
- Removed redundant slot availability check (UI already disables booked slots)
- Book Appt and Enquiry act as toggle tabs — one closes the other
- Dialler moved from FAB to header dropdown next to status toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 10:54:56 +05:30

170 lines
7.0 KiB
TypeScript

import { useState } from 'react';
import { faPenToSquare } from '@fortawesome/pro-duotone-svg-icons';
import { faIcon } from '@/lib/icon-wrapper';
import { SlideoutMenu } from '@/components/application/slideout-menus/slideout-menu';
import { Input } from '@/components/base/input/input';
import { Select } from '@/components/base/select/select';
import { Button } from '@/components/base/buttons/button';
import { apiClient } from '@/lib/api-client';
import { notify } from '@/lib/toast';
import type { Campaign, CampaignStatus } from '@/types/entities';
const PenIcon = faIcon(faPenToSquare);
type CampaignEditSlideoutProps = {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
campaign: Campaign;
onSaved?: () => void;
};
const statusItems = [
{ id: 'DRAFT' as const, label: 'Draft' },
{ id: 'ACTIVE' as const, label: 'Active' },
{ id: 'PAUSED' as const, label: 'Paused' },
{ id: 'COMPLETED' as const, label: 'Completed' },
];
const formatDateForInput = (dateStr: string | null): string => {
if (!dateStr) return '';
try {
return new Date(dateStr).toISOString().slice(0, 10);
} catch {
return '';
}
};
const budgetToDisplay = (campaign: Campaign): string => {
if (!campaign.budget) return '';
return String(Math.round(campaign.budget.amountMicros / 1_000_000));
};
export const CampaignEditSlideout = ({ isOpen, onOpenChange, campaign, onSaved }: CampaignEditSlideoutProps) => {
const [campaignName, setCampaignName] = useState(campaign.campaignName ?? '');
const [status, setStatus] = useState<CampaignStatus | null>(campaign.campaignStatus);
const [budget, setBudget] = useState(budgetToDisplay(campaign));
const [startDate, setStartDate] = useState(formatDateForInput(campaign.startDate));
const [endDate, setEndDate] = useState(formatDateForInput(campaign.endDate));
const [isSaving, setIsSaving] = useState(false);
const handleSave = async (close: () => void) => {
setIsSaving(true);
try {
const budgetMicros = budget ? Number(budget) * 1_000_000 : null;
await apiClient.graphql(
`mutation UpdateCampaign($id: UUID!, $data: CampaignUpdateInput!) {
updateCampaign(id: $id, data: $data) { id }
}`,
{
id: campaign.id,
data: {
campaignName: campaignName || null,
campaignStatus: status,
...(budgetMicros !== null
? {
budget: {
amountMicros: budgetMicros,
currencyCode: campaign.budget?.currencyCode ?? 'INR',
},
}
: {}),
startDate: startDate ? new Date(startDate).toISOString() : null,
endDate: endDate ? new Date(endDate).toISOString() : null,
},
},
);
notify.success('Campaign updated', `${campaignName || 'Campaign'} has been updated successfully.`);
onSaved?.();
close();
} catch (err) {
// apiClient.graphql already toasts on error
console.error('Failed to update campaign:', err);
} finally {
setIsSaving(false);
}
};
return (
<SlideoutMenu isOpen={isOpen} onOpenChange={onOpenChange} isDismissable>
{({ close }) => (
<>
<SlideoutMenu.Header onClose={close}>
<div className="flex items-center gap-3 pr-8">
<div className="flex size-10 items-center justify-center rounded-lg bg-brand-secondary">
<PenIcon className="size-5 text-fg-brand-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-primary">Edit Campaign</h2>
<p className="text-sm text-tertiary">Update campaign details</p>
</div>
</div>
</SlideoutMenu.Header>
<SlideoutMenu.Content>
<div className="flex flex-col gap-4">
<Input
label="Campaign Name"
placeholder="Enter campaign name"
value={campaignName}
onChange={setCampaignName}
/>
<Select
label="Status"
placeholder="Select status"
items={statusItems}
selectedKey={status}
onSelectionChange={(key) => setStatus(key as CampaignStatus)}
>
{(item) => <Select.Item id={item.id} label={item.label} />}
</Select>
<Input
label="Budget (INR)"
placeholder="e.g. 50000"
type="number"
value={budget}
onChange={setBudget}
/>
<div className="grid grid-cols-2 gap-3">
<Input
label="Start Date"
type="date"
value={startDate}
onChange={setStartDate}
/>
<Input
label="End Date"
type="date"
value={endDate}
onChange={setEndDate}
/>
</div>
</div>
</SlideoutMenu.Content>
<SlideoutMenu.Footer>
<div className="flex items-center justify-end gap-3">
<Button size="md" color="secondary" onClick={close}>
Cancel
</Button>
<Button
size="md"
color="primary"
isLoading={isSaving}
showTextWhileLoading
onClick={() => handleSave(close)}
>
{isSaving ? 'Saving...' : 'Save Changes'}
</Button>
</div>
</SlideoutMenu.Footer>
</>
)}
</SlideoutMenu>
);
};