Files
Krow-workspace/frontend-web/src/pages/EditPartner.jsx
bwnyasse 80cd49deb5 feat(Makefile): install frontend dependencies on dev command
feat(Makefile): patch Layout.jsx queryKey for local development
feat(frontend-web): mock base44 client for local development with role switching
feat(frontend-web): add event assignment modal with conflict detection and bulk assign
feat(frontend-web): add client dashboard with key metrics and quick actions
feat(frontend-web): add layout component with role-based navigation
feat(frontend-web): update various pages to use "@/components" alias
feat(frontend-web): update create event page with ai assistant toggle
feat(frontend-web): update dashboard page with new components
feat(frontend-web): update events page with quick assign popover
feat(frontend-web): update invite vendor page with hover card
feat(frontend-web): update messages page with conversation list and message thread
feat(frontend-web): update operator dashboard page with new components
feat(frontend-web): update partner management page with new components
feat(frontend-web): update permissions page with new components
feat(frontend-web): update procurement dashboard page with new components
feat(frontend-web): update smart vendor onboarding page with new components
feat(frontend-web): update staff directory page with new components
feat(frontend-web): update teams page with new components
feat(frontend-web): update user management page with new components
feat(frontend-web): update vendor compliance page with new components
feat(frontend-web): update main.jsx to include react query provider

feat: add vendor marketplace page
feat: add global import fix to prepare-export script
feat: add patch-layout-query-key script to fix query key
feat: update patch-base44-client script to use a more robust method
2025-11-13 14:56:31 -05:00

375 lines
14 KiB
JavaScript

import React, { useState } from "react";
import { base44 } from "@/api/base44Client";
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
import { Briefcase, ArrowLeft, Save, Plus, X, Loader2 } from "lucide-react";
import PageHeader from "@/components/common/PageHeader";
import { useToast } from "@/components/ui/use-toast";
export default function EditPartner() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { toast } = useToast();
const urlParams = new URLSearchParams(window.location.search);
const partnerId = urlParams.get('id');
const { data: partners = [] } = useQuery({
queryKey: ['partners'],
queryFn: () => base44.entities.Partner.list(),
initialData: [],
});
const { data: sectors = [] } = useQuery({
queryKey: ['sectors'],
queryFn: () => base44.entities.Sector.list(),
initialData: [],
});
const partner = partners.find(p => p.id === partnerId);
const [partnerData, setPartnerData] = useState({
partner_name: "",
partner_number: "",
partner_type: "Corporate",
sector_id: "",
sector_name: "",
primary_contact_name: "",
primary_contact_email: "",
primary_contact_phone: "",
billing_address: "",
sites: [],
payment_terms: "Net 30",
is_active: true
});
const [newSite, setNewSite] = useState({
site_name: "",
address: "",
city: "",
state: "",
zip_code: "",
site_manager: "",
site_manager_email: ""
});
React.useEffect(() => {
if (partner) {
setPartnerData({
partner_name: partner.partner_name || "",
partner_number: partner.partner_number || "",
partner_type: partner.partner_type || "Corporate",
sector_id: partner.sector_id || "",
sector_name: partner.sector_name || "",
primary_contact_name: partner.primary_contact_name || "",
primary_contact_email: partner.primary_contact_email || "",
primary_contact_phone: partner.primary_contact_phone || "",
billing_address: partner.billing_address || "",
sites: partner.sites || [],
payment_terms: partner.payment_terms || "Net 30",
is_active: partner.is_active !== undefined ? partner.is_active : true
});
}
}, [partner]);
const updatePartnerMutation = useMutation({
mutationFn: ({ id, data }) => base44.entities.Partner.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['partners'] });
toast({
title: "Partner Updated",
description: "Partner information has been successfully updated",
});
navigate(createPageUrl("PartnerManagement"));
},
});
const handleSubmit = (e) => {
e.preventDefault();
updatePartnerMutation.mutate({ id: partnerId, data: partnerData });
};
const handleSectorChange = (sectorId) => {
const sector = sectors.find(s => s.id === sectorId);
setPartnerData({
...partnerData,
sector_id: sectorId,
sector_name: sector?.sector_name || ""
});
};
const handleAddSite = () => {
if (newSite.site_name && newSite.address) {
setPartnerData({
...partnerData,
sites: [...partnerData.sites, newSite]
});
setNewSite({
site_name: "",
address: "",
city: "",
state: "",
zip_code: "",
site_manager: "",
site_manager_email: ""
});
}
};
const handleRemoveSite = (index) => {
setPartnerData({
...partnerData,
sites: partnerData.sites.filter((_, i) => i !== index)
});
};
if (!partner) {
return (
<div className="p-8 text-center">
<Loader2 className="w-8 h-8 animate-spin text-[#0A39DF] mx-auto mb-4" />
<h2 className="text-2xl font-bold text-slate-900 mb-4">Loading Partner...</h2>
</div>
);
}
return (
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
<div className="max-w-4xl mx-auto">
<PageHeader
title="Edit Partner"
subtitle={`Update information for ${partner.partner_name}`}
backTo={createPageUrl("PartnerManagement")}
backButtonLabel="Back to Partners"
/>
<form onSubmit={handleSubmit}>
<Card className="mb-6 border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b">
<CardTitle className="flex items-center gap-2">
<Briefcase className="w-5 h-5 text-[#0A39DF]" />
Basic Information
</CardTitle>
</CardHeader>
<CardContent className="p-6 space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<Label htmlFor="partner_number">Partner Number</Label>
<Input
id="partner_number"
value={partnerData.partner_number}
readOnly
className="bg-slate-50 font-mono"
/>
<p className="text-xs text-slate-500 mt-1">Auto-generated unique ID</p>
</div>
<div>
<Label htmlFor="partner_type">Partner Type *</Label>
<Select onValueChange={(value) => setPartnerData({...partnerData, partner_type: value})} value={partnerData.partner_type}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Corporate">Corporate</SelectItem>
<SelectItem value="Education">Education</SelectItem>
<SelectItem value="Healthcare">Healthcare</SelectItem>
<SelectItem value="Sports & Entertainment">Sports & Entertainment</SelectItem>
<SelectItem value="Government">Government</SelectItem>
</SelectContent>
</Select>
</div>
<div className="md:col-span-2">
<Label htmlFor="partner_name">Partner Name *</Label>
<Input
id="partner_name"
placeholder="e.g., Google"
value={partnerData.partner_name}
onChange={(e) => setPartnerData({...partnerData, partner_name: e.target.value})}
required
/>
</div>
<div>
<Label htmlFor="sector">Sector</Label>
<Select onValueChange={handleSectorChange} value={partnerData.sector_id}>
<SelectTrigger>
<SelectValue placeholder="Select sector" />
</SelectTrigger>
<SelectContent>
{sectors.map((sector) => (
<SelectItem key={sector.id} value={sector.id}>
{sector.sector_name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="payment_terms">Payment Terms</Label>
<Input
id="payment_terms"
placeholder="Net 30"
value={partnerData.payment_terms}
onChange={(e) => setPartnerData({...partnerData, payment_terms: e.target.value})}
/>
</div>
<div>
<Label htmlFor="primary_contact_name">Primary Contact Name</Label>
<Input
id="primary_contact_name"
placeholder="John Doe"
value={partnerData.primary_contact_name}
onChange={(e) => setPartnerData({...partnerData, primary_contact_name: e.target.value})}
/>
</div>
<div>
<Label htmlFor="primary_contact_email">Primary Contact Email</Label>
<Input
id="primary_contact_email"
type="email"
placeholder="john@company.com"
value={partnerData.primary_contact_email}
onChange={(e) => setPartnerData({...partnerData, primary_contact_email: e.target.value})}
/>
</div>
<div>
<Label htmlFor="primary_contact_phone">Primary Contact Phone</Label>
<Input
id="primary_contact_phone"
placeholder="(555) 123-4567"
value={partnerData.primary_contact_phone}
onChange={(e) => setPartnerData({...partnerData, primary_contact_phone: e.target.value})}
/>
</div>
<div className="md:col-span-2">
<div className="flex items-center justify-between">
<Label htmlFor="is_active">Active Status</Label>
<Switch
id="is_active"
checked={partnerData.is_active}
onCheckedChange={(checked) => setPartnerData({...partnerData, is_active: checked})}
/>
</div>
<p className="text-xs text-slate-500 mt-1">Toggle to activate or deactivate this partner</p>
</div>
</div>
<div>
<Label htmlFor="billing_address">Billing Address</Label>
<Textarea
id="billing_address"
placeholder="123 Main St, City, State ZIP"
value={partnerData.billing_address}
onChange={(e) => setPartnerData({...partnerData, billing_address: e.target.value})}
rows={2}
/>
</div>
</CardContent>
</Card>
<Card className="mb-6 border-slate-200 shadow-lg">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b">
<CardTitle>Sites/Locations</CardTitle>
</CardHeader>
<CardContent className="p-6 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-slate-50 rounded-lg">
<Input
placeholder="Site Name"
value={newSite.site_name}
onChange={(e) => setNewSite({...newSite, site_name: e.target.value})}
/>
<Input
placeholder="Address"
value={newSite.address}
onChange={(e) => setNewSite({...newSite, address: e.target.value})}
/>
<Input
placeholder="City"
value={newSite.city}
onChange={(e) => setNewSite({...newSite, city: e.target.value})}
/>
<Input
placeholder="State"
value={newSite.state}
onChange={(e) => setNewSite({...newSite, state: e.target.value})}
/>
<Input
placeholder="ZIP Code"
value={newSite.zip_code}
onChange={(e) => setNewSite({...newSite, zip_code: e.target.value})}
/>
<Button type="button" onClick={handleAddSite} variant="outline" className="w-full">
<Plus className="w-4 h-4 mr-2" />
Add Site
</Button>
</div>
{partnerData.sites.length > 0 && (
<div className="space-y-2">
{partnerData.sites.map((site, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-white border rounded-lg">
<div>
<p className="font-semibold">{site.site_name}</p>
<p className="text-sm text-slate-500">{site.address}, {site.city}, {site.state} {site.zip_code}</p>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveSite(index)}
>
<X className="w-4 h-4" />
</Button>
</div>
))}
</div>
)}
</CardContent>
</Card>
<div className="flex justify-end gap-3">
<Button
type="button"
variant="outline"
onClick={() => navigate(createPageUrl("PartnerManagement"))}
>
<ArrowLeft className="w-4 h-4 mr-2" />
Cancel
</Button>
<Button
type="submit"
className="bg-[#0A39DF] hover:bg-[#0A39DF]/90"
disabled={updatePartnerMutation.isPending}
>
{updatePartnerMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Updating...
</>
) : (
<>
<Save className="w-4 h-4 mr-2" />
Update Partner
</>
)}
</Button>
</div>
</form>
</div>
</div>
);
}