mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
426 lines
15 KiB
TypeScript
426 lines
15 KiB
TypeScript
import { Button } from '@/components/ui';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
|
import Checkbox from '@/components/ui/Checkbox';
|
|
import ImageUpload from '@/components/ui/ImageUpload';
|
|
import Input from '@/components/ui/Input';
|
|
import { Stack } from '@/components/ui/layout';
|
|
import MapPicker from '@/components/ui/MapPicker';
|
|
import Select from '@/components/ui/Select';
|
|
import Textarea from '@/components/ui/Textarea';
|
|
import { useCreateOrganization, useUpdateOrganization } from '@/hooks/api/useOrganizationsAPI.ts';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import { useOrganizations } from '@/hooks/useOrganizations.ts';
|
|
import { useToast } from '@/hooks/useToast.ts';
|
|
import { Organization } from '@/types.ts';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { ArrowLeft, Save } from 'lucide-react';
|
|
import { useEffect, useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import { z } from 'zod';
|
|
|
|
// Form validation schema
|
|
const organizationSchema = z.object({
|
|
name: z.string().min(1, 'Name is required'),
|
|
sector: z.string().min(1, 'Sector is required'),
|
|
subtype: z.string().optional(),
|
|
description: z.string().optional(),
|
|
website: z.string().url().optional().or(z.literal('')),
|
|
address: z
|
|
.object({
|
|
street: z.string().optional(),
|
|
city: z.string().optional(),
|
|
state: z.string().optional(),
|
|
zip: z.string().optional(),
|
|
country: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
coordinates: z
|
|
.object({
|
|
lat: z.number(),
|
|
lng: z.number(),
|
|
})
|
|
.optional(),
|
|
needs: z.array(z.string()).optional(),
|
|
offers: z.array(z.string()).optional(),
|
|
logo: z.string().optional(),
|
|
verified: z.boolean().optional(),
|
|
});
|
|
|
|
type OrganizationFormData = z.infer<typeof organizationSchema>;
|
|
|
|
const AdminOrganizationEditPage = () => {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const { id } = useParams<{ id: string }>();
|
|
const isEditing = !!id;
|
|
|
|
const { organizations } = useOrganizations();
|
|
const { success, error: showError } = useToast();
|
|
|
|
const createOrganization = useCreateOrganization();
|
|
const updateOrganization = useUpdateOrganization();
|
|
|
|
const [currentOrg, setCurrentOrg] = useState<Organization | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const form = useForm<OrganizationFormData>({
|
|
resolver: zodResolver(organizationSchema),
|
|
defaultValues: {
|
|
name: '',
|
|
sector: '',
|
|
subtype: '',
|
|
description: '',
|
|
website: '',
|
|
address: {
|
|
street: '',
|
|
city: '',
|
|
state: '',
|
|
zip: '',
|
|
country: '',
|
|
},
|
|
coordinates: undefined,
|
|
needs: [],
|
|
offers: [],
|
|
logo: '',
|
|
verified: false,
|
|
},
|
|
});
|
|
|
|
// Load organization data for editing
|
|
useEffect(() => {
|
|
if (isEditing && organizations.length > 0) {
|
|
const org = organizations.find((o) => o.ID === id);
|
|
if (org) {
|
|
setCurrentOrg(org);
|
|
form.reset({
|
|
name: org.Name || '',
|
|
sector: org.Sector || '',
|
|
subtype: org.Subtype || '',
|
|
description: org.Description || '',
|
|
website: org.Website || '',
|
|
address: {
|
|
street: org.Address?.split(',')[0]?.trim() || '',
|
|
city: org.Address?.split(',')[1]?.trim() || '',
|
|
state: org.Address?.split(',')[2]?.split(' ')[0]?.trim() || '',
|
|
zip: org.Address?.split(',')[2]?.split(' ')[1]?.trim() || '',
|
|
country: 'Russia', // Default
|
|
},
|
|
coordinates: org.Coordinates
|
|
? {
|
|
lat: org.Coordinates.lat,
|
|
lng: org.Coordinates.lng,
|
|
}
|
|
: undefined,
|
|
needs: org.Needs || [],
|
|
offers: org.Offers || [],
|
|
logo: org.Logo || '',
|
|
verified: org.Verified || false,
|
|
});
|
|
}
|
|
}
|
|
}, [id, isEditing, organizations, form]);
|
|
|
|
const onSubmit = async (data: OrganizationFormData) => {
|
|
setIsLoading(true);
|
|
try {
|
|
const payload = {
|
|
name: data.name,
|
|
sector: data.sector,
|
|
subtype: data.subtype,
|
|
description: data.description,
|
|
website: data.website,
|
|
address: data.address
|
|
? `${data.address.street}, ${data.address.city}, ${data.address.state} ${data.address.zip}`.trim()
|
|
: '',
|
|
coordinates: data.coordinates,
|
|
needs: data.needs,
|
|
offers: data.offers,
|
|
logo: data.logo,
|
|
verified: data.verified,
|
|
};
|
|
|
|
if (isEditing && currentOrg) {
|
|
await updateOrganization.mutateAsync({
|
|
id: currentOrg.ID,
|
|
data: payload,
|
|
});
|
|
success('Organization updated successfully');
|
|
} else {
|
|
await createOrganization.mutateAsync(payload);
|
|
success('Organization created successfully');
|
|
}
|
|
|
|
navigate('/admin/organizations');
|
|
} catch (err) {
|
|
console.error('Error saving organization:', err);
|
|
showError('Failed to save organization', {
|
|
title: 'An error occurred while saving the organization',
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const sectors = [
|
|
{ value: 'technology', label: 'Technology' },
|
|
{ value: 'manufacturing', label: 'Manufacturing' },
|
|
{ value: 'agriculture', label: 'Agriculture' },
|
|
{ value: 'energy', label: 'Energy' },
|
|
{ value: 'construction', label: 'Construction' },
|
|
{ value: 'transportation', label: 'Transportation' },
|
|
{ value: 'healthcare', label: 'Healthcare' },
|
|
{ value: 'education', label: 'Education' },
|
|
{ value: 'finance', label: 'Finance' },
|
|
{ value: 'retail', label: 'Retail' },
|
|
{ value: 'hospitality', label: 'Hospitality' },
|
|
{ value: 'other', label: 'Other' },
|
|
];
|
|
|
|
const subtypes = [
|
|
{ value: 'company', label: 'Company' },
|
|
{ value: 'nonprofit', label: 'Non-profit Organization' },
|
|
{ value: 'government', label: 'Government Agency' },
|
|
{ value: 'educational', label: 'Educational Institution' },
|
|
{ value: 'research', label: 'Research Institution' },
|
|
];
|
|
|
|
const commonResources = [
|
|
'Raw materials',
|
|
'Energy',
|
|
'Water',
|
|
'Waste management',
|
|
'Transportation',
|
|
'Equipment',
|
|
'Technology services',
|
|
'Consulting',
|
|
'Training',
|
|
'Financial services',
|
|
];
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="sm" onClick={() => navigate('/admin/organizations')}>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
Back to Organizations
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-2xl font-bold">
|
|
{isEditing ? 'Edit Organization' : 'Create New Organization'}
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
{isEditing
|
|
? 'Update organization details'
|
|
: 'Add a new organization to the ecosystem'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" onClick={() => navigate('/admin/organizations')}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={form.handleSubmit(onSubmit)} disabled={isLoading}>
|
|
<Save className="w-4 h-4 mr-2" />
|
|
{isLoading ? 'Saving...' : 'Save Organization'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
|
<Stack spacing="lg">
|
|
{/* Basic Information */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Basic Information</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Organization Name *</label>
|
|
<Input
|
|
{...form.register('name')}
|
|
placeholder="Enter organization name"
|
|
error={form.formState.errors.name?.message}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Sector *</label>
|
|
<Select
|
|
value={form.watch('sector')}
|
|
onChange={(value) => form.setValue('sector', value)}
|
|
options={sectors}
|
|
placeholder="Select sector"
|
|
error={form.formState.errors.sector?.message}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Subtype</label>
|
|
<Select
|
|
value={form.watch('subtype')}
|
|
onChange={(value) => form.setValue('subtype', value)}
|
|
options={subtypes}
|
|
placeholder="Select organization type"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Description</label>
|
|
<Textarea
|
|
{...form.register('description')}
|
|
placeholder="Describe what this organization does..."
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Website</label>
|
|
<Input
|
|
{...form.register('website')}
|
|
placeholder="https://example.com"
|
|
error={form.formState.errors.website?.message}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Location */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Location</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Street Address</label>
|
|
<Input {...form.register('address.street')} placeholder="Street address" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">City</label>
|
|
<Input {...form.register('address.city')} placeholder="City" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">State/Region</label>
|
|
<Input {...form.register('address.state')} placeholder="State or region" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">ZIP/Postal Code</label>
|
|
<Input {...form.register('address.zip')} placeholder="ZIP or postal code" />
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Location on Map</label>
|
|
<MapPicker
|
|
value={form.watch('coordinates')}
|
|
onChange={(coords) => form.setValue('coordinates', coords)}
|
|
height="200px"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Resources */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Resources</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium mb-2">
|
|
What does this organization need?
|
|
</label>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{commonResources.map((resource) => (
|
|
<Checkbox
|
|
key={`need-${resource}`}
|
|
label={resource}
|
|
checked={form.watch('needs')?.includes(resource) || false}
|
|
onChange={(checked) => {
|
|
const currentNeeds = form.watch('needs') || [];
|
|
if (checked) {
|
|
form.setValue('needs', [...currentNeeds, resource]);
|
|
} else {
|
|
form.setValue(
|
|
'needs',
|
|
currentNeeds.filter((n) => n !== resource)
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium mb-2">
|
|
What does this organization offer?
|
|
</label>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
|
{commonResources.map((resource) => (
|
|
<Checkbox
|
|
key={`offer-${resource}`}
|
|
label={resource}
|
|
checked={form.watch('offers')?.includes(resource) || false}
|
|
onChange={(checked) => {
|
|
const currentOffers = form.watch('offers') || [];
|
|
if (checked) {
|
|
form.setValue('offers', [...currentOffers, resource]);
|
|
} else {
|
|
form.setValue(
|
|
'offers',
|
|
currentOffers.filter((o) => o !== resource)
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Media */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Logo & Branding</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div>
|
|
<label className="block text-sm font-medium mb-1">Organization Logo</label>
|
|
<ImageUpload
|
|
value={form.watch('logo')}
|
|
onChange={(url) => form.setValue('logo', url)}
|
|
accept="image/*"
|
|
maxSize={5 * 1024 * 1024} // 5MB
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Verification */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Verification Status</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Checkbox
|
|
label="Verified Organization"
|
|
checked={form.watch('verified') || false}
|
|
onChange={(checked) => form.setValue('verified', checked)}
|
|
description="Mark this organization as verified and approved"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</Stack>
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminOrganizationEditPage;
|