activity type added in admi settings

This commit is contained in:
laxmanhalaki 2026-01-07 18:56:16 +05:30
parent 94b7c34a7a
commit d725e523b3
7 changed files with 651 additions and 26 deletions

View File

@ -0,0 +1,451 @@
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
FileText,
Plus,
Trash2,
Edit2,
Loader2,
AlertCircle,
CheckCircle,
} from 'lucide-react';
import {
getAllActivityTypes,
createActivityType,
updateActivityType,
deleteActivityType,
ActivityType
} from '@/services/adminApi';
import { toast } from 'sonner';
export function ActivityTypeManager() {
const [activityTypes, setActivityTypes] = useState<ActivityType[]>([]);
const [loading, setLoading] = useState(true);
const [showAddDialog, setShowAddDialog] = useState(false);
const [editingActivityType, setEditingActivityType] = useState<ActivityType | null>(null);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [formData, setFormData] = useState({
title: '',
itemCode: '',
taxationType: '',
sapRefNo: ''
});
useEffect(() => {
loadActivityTypes();
}, []);
const loadActivityTypes = async () => {
try {
setLoading(true);
setError(null);
const data = await getAllActivityTypes(false); // Get all including inactive
setActivityTypes(data);
} catch (err: any) {
const errorMsg = err.response?.data?.error || 'Failed to load activity types';
setError(errorMsg);
toast.error(errorMsg);
} finally {
setLoading(false);
}
};
const handleAdd = () => {
setFormData({
title: '',
itemCode: '',
taxationType: '',
sapRefNo: ''
});
setEditingActivityType(null);
setShowAddDialog(true);
};
const handleEdit = (activityType: ActivityType) => {
setFormData({
title: activityType.title,
itemCode: activityType.itemCode || '',
taxationType: activityType.taxationType || '',
sapRefNo: activityType.sapRefNo || ''
});
setEditingActivityType(activityType);
setShowAddDialog(true);
};
const handleSave = async () => {
try {
setError(null);
if (!formData.title.trim()) {
setError('Activity type title is required');
return;
}
const payload: Partial<ActivityType> = {
title: formData.title.trim(),
itemCode: formData.itemCode.trim() || null,
taxationType: formData.taxationType.trim() || null,
sapRefNo: formData.sapRefNo.trim() || null
};
if (editingActivityType) {
// Update existing
await updateActivityType(editingActivityType.activityTypeId, payload);
setSuccessMessage('Activity type updated successfully');
toast.success('Activity type updated successfully');
} else {
// Create new
await createActivityType(payload);
setSuccessMessage('Activity type created successfully');
toast.success('Activity type created successfully');
}
await loadActivityTypes();
setShowAddDialog(false);
setTimeout(() => setSuccessMessage(null), 3000);
} catch (err: any) {
const errorMsg = err.response?.data?.error || 'Failed to save activity type';
setError(errorMsg);
toast.error(errorMsg);
}
};
const handleDelete = async (activityType: ActivityType) => {
if (!confirm(`Delete "${activityType.title}"? This will deactivate the activity type.`)) {
return;
}
try {
setError(null);
await deleteActivityType(activityType.activityTypeId);
setSuccessMessage('Activity type deleted successfully');
toast.success('Activity type deleted successfully');
await loadActivityTypes();
setTimeout(() => setSuccessMessage(null), 3000);
} catch (err: any) {
const errorMsg = err.response?.data?.error || 'Failed to delete activity type';
setError(errorMsg);
toast.error(errorMsg);
}
};
// Filter active and inactive activity types
const activeActivityTypes = activityTypes.filter(at => at.isActive !== false && at.isActive !== undefined);
const inactiveActivityTypes = activityTypes.filter(at => at.isActive === false);
return (
<div className="space-y-6">
{/* Success Message */}
{successMessage && (
<div className="p-4 bg-gradient-to-r from-green-50 to-emerald-50 border border-green-300 rounded-md shadow-sm flex items-center gap-3 animate-in fade-in slide-in-from-top-2 duration-300">
<div className="p-1.5 bg-green-500 rounded-md">
<CheckCircle className="w-4 h-4 text-white shrink-0" />
</div>
<p className="text-sm font-medium text-green-900">{successMessage}</p>
</div>
)}
{/* Error Message */}
{error && (
<div className="p-4 bg-gradient-to-r from-red-50 to-rose-50 border border-red-300 rounded-md shadow-sm flex items-center gap-3 animate-in fade-in slide-in-from-top-2 duration-300">
<div className="p-1.5 bg-red-500 rounded-md">
<AlertCircle className="w-4 h-4 text-white shrink-0" />
</div>
<p className="text-sm font-medium text-red-900">{error}</p>
<Button
size="sm"
variant="ghost"
onClick={() => setError(null)}
className="ml-auto hover:bg-red-100"
>
Dismiss
</Button>
</div>
)}
{/* Header */}
<Card className="shadow-lg border-0 rounded-md">
<CardHeader className="border-b border-slate-100 py-4 sm:py-5">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-gradient-to-br from-slate-600 to-slate-700 rounded-md shadow-md">
<FileText className="w-5 h-5 text-white" />
</div>
<div>
<CardTitle className="text-lg sm:text-xl font-semibold text-slate-900">Activity Types</CardTitle>
<CardDescription className="text-sm">
Manage dealer claim activity types
</CardDescription>
</div>
</div>
<Button
onClick={handleAdd}
className="gap-2 bg-re-green hover:bg-re-green/90 text-white shadow-sm"
>
<Plus className="w-4 h-4" />
<span className="hidden xs:inline">Add Activity Type</span>
<span className="xs:hidden">Add</span>
</Button>
</div>
</CardHeader>
</Card>
{/* Activity Types List */}
{loading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-gray-400" />
</div>
) : activeActivityTypes.length === 0 ? (
<Card className="shadow-lg border-0 rounded-md">
<CardContent className="p-12 text-center">
<div className="p-4 bg-slate-100 rounded-full w-20 h-20 flex items-center justify-center mx-auto mb-4">
<FileText className="w-10 h-10 text-slate-400" />
</div>
<p className="text-slate-700 font-medium text-lg">No activity types found</p>
<p className="text-sm text-slate-500 mt-2 mb-6">Add activity types for dealer claim management</p>
<Button
onClick={handleAdd}
variant="outline"
className="gap-2 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
>
<Plus className="w-4 h-4" />
Add First Activity Type
</Button>
</CardContent>
</Card>
) : (
<div className="space-y-4 sm:space-y-6">
{/* Active Activity Types */}
<Card className="shadow-lg border-0 rounded-md">
<CardHeader className="pb-3 sm:pb-4 border-b border-slate-100">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base sm:text-lg font-semibold text-slate-900">Active Activity Types</CardTitle>
<CardDescription className="text-xs sm:text-sm">
{activeActivityTypes.length} active type{activeActivityTypes.length !== 1 ? 's' : ''}
</CardDescription>
</div>
<div className="p-2 bg-green-50 rounded-md">
<CheckCircle className="w-4 h-4 text-green-600" />
</div>
</div>
</CardHeader>
<CardContent className="space-y-3 pt-4">
{activeActivityTypes.map(activityType => (
<div
key={activityType.activityTypeId}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-slate-50 border border-slate-200 rounded-md hover:bg-slate-100 hover:border-slate-300 transition-all shadow-sm"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap">
<p className="font-semibold text-slate-900 text-sm sm:text-base">{activityType.title}</p>
<Badge variant="outline" className="bg-gradient-to-r from-green-50 to-emerald-50 text-green-800 border-green-300 text-[10px] sm:text-xs font-medium shadow-sm">
Active
</Badge>
</div>
<div className="flex flex-wrap gap-3 text-xs sm:text-sm text-slate-600">
{activityType.itemCode && (
<span className="font-medium">Item Code: <span className="text-slate-900">{activityType.itemCode}</span></span>
)}
{activityType.taxationType && (
<span className="font-medium">Taxation: <span className="text-slate-900">{activityType.taxationType}</span></span>
)}
{activityType.sapRefNo && (
<span className="font-medium">SAP Ref: <span className="text-slate-900">{activityType.sapRefNo}</span></span>
)}
{!activityType.itemCode && !activityType.taxationType && !activityType.sapRefNo && (
<span className="text-slate-500 italic">No additional details</span>
)}
</div>
</div>
<div className="flex items-center gap-2 sm:gap-2 self-end sm:self-auto">
<Button
size="sm"
variant="ghost"
onClick={() => handleEdit(activityType)}
className="gap-1.5 hover:bg-blue-50 border border-transparent hover:border-blue-200 text-xs sm:text-sm"
>
<Edit2 className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
<span className="hidden xs:inline">Edit</span>
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleDelete(activityType)}
className="gap-1.5 text-red-600 hover:text-red-700 hover:bg-red-50 border border-transparent hover:border-red-200 text-xs sm:text-sm"
>
<Trash2 className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
<span className="hidden xs:inline">Delete</span>
</Button>
</div>
</div>
))}
</CardContent>
</Card>
{/* Inactive Activity Types */}
{inactiveActivityTypes.length > 0 && (
<Card className="shadow-lg border-0 rounded-md border-amber-200">
<CardHeader className="pb-3 sm:pb-4 border-b border-slate-100">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base sm:text-lg font-semibold text-slate-900">Inactive Activity Types</CardTitle>
<CardDescription className="text-xs sm:text-sm">
{inactiveActivityTypes.length} inactive type{inactiveActivityTypes.length !== 1 ? 's' : ''}
</CardDescription>
</div>
<div className="p-2 bg-amber-50 rounded-md">
<AlertCircle className="w-4 h-4 text-amber-600" />
</div>
</div>
</CardHeader>
<CardContent className="space-y-3 pt-4">
{inactiveActivityTypes.map(activityType => (
<div
key={activityType.activityTypeId}
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-amber-50/50 border border-amber-200 rounded-md hover:bg-amber-50 hover:border-amber-300 transition-all shadow-sm"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap">
<p className="font-semibold text-slate-700 text-sm sm:text-base line-through">{activityType.title}</p>
<Badge variant="outline" className="bg-gradient-to-r from-amber-50 to-orange-50 text-amber-800 border-amber-300 text-[10px] sm:text-xs font-medium shadow-sm">
Inactive
</Badge>
</div>
</div>
<div className="flex items-center gap-2 sm:gap-2 self-end sm:self-auto">
<Button
size="sm"
variant="ghost"
onClick={() => handleEdit(activityType)}
className="gap-1.5 hover:bg-blue-50 border border-transparent hover:border-blue-200 text-xs sm:text-sm"
>
<Edit2 className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
<span className="hidden xs:inline">Edit</span>
</Button>
</div>
</div>
))}
</CardContent>
</Card>
)}
</div>
)}
{/* Add/Edit Dialog */}
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
<DialogContent className="sm:max-w-[550px] max-h-[90vh] rounded-lg flex flex-col p-0">
<DialogHeader className="pb-4 border-b border-slate-100 px-6 pt-6 flex-shrink-0">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-gradient-to-br from-slate-600 to-slate-700 rounded-lg shadow-md">
<FileText className="w-5 h-5 text-white" />
</div>
<div className="flex-1">
<DialogTitle className="text-xl font-semibold text-slate-900">
{editingActivityType ? 'Edit Activity Type' : 'Add New Activity Type'}
</DialogTitle>
<DialogDescription className="text-sm text-slate-600 mt-1">
{editingActivityType ? 'Update activity type information' : 'Add a new activity type for dealer claim management'}
</DialogDescription>
</div>
</div>
</DialogHeader>
<div className="space-y-5 py-6 px-6 overflow-y-auto flex-1 min-h-0">
{/* Title Field */}
<div className="space-y-2">
<Label htmlFor="title" className="text-sm font-semibold text-slate-900 flex items-center gap-1">
Title <span className="text-red-500">*</span>
</Label>
<Input
id="title"
placeholder="e.g., Riders Mania Claims, Legal Claims Reimbursement"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm"
/>
<p className="text-xs text-slate-500">Enter the activity type title</p>
</div>
{/* Item Code Field */}
<div className="space-y-2">
<Label htmlFor="itemCode" className="text-sm font-semibold text-slate-900">
Item Code <span className="text-slate-400 font-normal text-xs">(Optional)</span>
</Label>
<Input
id="itemCode"
placeholder="e.g., 1, 2, 3"
value={formData.itemCode}
onChange={(e) => setFormData({ ...formData, itemCode: e.target.value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm"
/>
<p className="text-xs text-slate-500">Optional item code for the activity type</p>
</div>
{/* Taxation Type Field */}
<div className="space-y-2">
<Label htmlFor="taxationType" className="text-sm font-semibold text-slate-900">
Taxation Type <span className="text-slate-400 font-normal text-xs">(Optional)</span>
</Label>
<Input
id="taxationType"
placeholder="e.g., GST, VAT, Exempt"
value={formData.taxationType}
onChange={(e) => setFormData({ ...formData, taxationType: e.target.value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm"
/>
<p className="text-xs text-slate-500">Optional taxation type for the activity</p>
</div>
{/* SAP Reference Number Field */}
<div className="space-y-2">
<Label htmlFor="sapRefNo" className="text-sm font-semibold text-slate-900">
SAP Reference Number <span className="text-slate-400 font-normal text-xs">(Optional)</span>
</Label>
<Input
id="sapRefNo"
placeholder="e.g., SAP-12345"
value={formData.sapRefNo}
onChange={(e) => setFormData({ ...formData, sapRefNo: e.target.value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm"
/>
<p className="text-xs text-slate-500">Optional SAP reference number</p>
</div>
</div>
<DialogFooter className="gap-3 pt-4 border-t border-slate-100 px-6 pb-6 flex-shrink-0">
<Button
variant="outline"
onClick={() => setShowAddDialog(false)}
className="h-11 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
>
Cancel
</Button>
<Button
onClick={handleSave}
disabled={!formData.title.trim()}
className="h-11 bg-re-green hover:bg-re-green/90 text-white shadow-md hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
<FileText className="w-4 h-4 mr-2" />
{editingActivityType ? 'Update Activity Type' : 'Add Activity Type'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@ -1,3 +1,4 @@
export { ConfigurationManager } from './ConfigurationManager';
export { HolidayManager } from './HolidayManager';
export { ActivityTypeManager } from './ActivityTypeManager';

View File

@ -278,7 +278,7 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
};
const needsClosure = (request?.status === 'approved' || request?.status === 'rejected') && isInitiator;
const isClosed = request?.status === 'closed' || (request?.status === 'approved' && !isInitiator) || (request?.status === 'rejected' && !isInitiator);
const isClosed = request?.status === 'closed';
// Fetch summary details if request is closed
useEffect(() => {

View File

@ -32,7 +32,7 @@ import { toast } from 'sonner';
import { getAllDealers as fetchDealersFromAPI, verifyDealerLogin, type DealerInfo } from '@/services/dealerApi';
import { ClaimApproverSelectionStep } from './ClaimApproverSelectionStep';
import { useAuth } from '@/contexts/AuthContext';
import { getPublicConfigurations, AdminConfiguration } from '@/services/adminApi';
import { getPublicConfigurations, AdminConfiguration, getActivityTypes, type ActivityType } from '@/services/adminApi';
import { PolicyViolationModal } from '@/components/modals/PolicyViolationModal';
// CLAIM_STEPS definition (same as in ClaimApproverSelectionStep)
@ -52,22 +52,6 @@ interface ClaimManagementWizardProps {
onSubmit?: (claimData: any) => void;
}
const CLAIM_TYPES = [
'Riders Mania Claims',
'Marketing Cost Bike to Vendor',
'Media Bike Service',
'ARAI Motorcycle Liquidation',
'ARAI Certification STA Approval CNR',
'Procurement of Spares/Apparel/GMA for Events',
'Fuel for Media Bike Used for Event',
'Motorcycle Buyback and Goodwill Support',
'Liquidation of Used Motorcycle',
'Motorcycle Registration CNR (Owned or Gifted by RE)',
'Legal Claims Reimbursement',
'Service Camp Claims',
'Corporate Claims Institutional Sales PDI'
];
const STEP_NAMES = [
'Claim Details',
'Approver Selection',
@ -101,6 +85,27 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
violations: []
});
const [activityTypes, setActivityTypes] = useState<ActivityType[]>([]);
const [loadingActivityTypes, setLoadingActivityTypes] = useState(true);
// Load activity types from API on mount
useEffect(() => {
const loadActivityTypes = async () => {
try {
setLoadingActivityTypes(true);
const types = await getActivityTypes();
setActivityTypes(types);
} catch (error) {
console.error('Failed to load activity types:', error);
toast.error('Failed to load activity types. Please refresh the page.');
} finally {
setLoadingActivityTypes(false);
}
};
loadActivityTypes();
}, []);
// Load system policy on mount
useEffect(() => {
const loadSystemPolicy = async () => {
@ -481,14 +486,26 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
<div>
<Label htmlFor="activityType" className="text-base font-semibold">Activity Type *</Label>
<Select value={formData.activityType} onValueChange={(value) => updateFormData('activityType', value)}>
<Select
value={formData.activityType}
onValueChange={(value) => updateFormData('activityType', value)}
disabled={loadingActivityTypes}
>
<SelectTrigger className="mt-2 !h-12 data-[size=default]:!h-12" id="activityType">
<SelectValue placeholder="Select activity type" />
<SelectValue placeholder={loadingActivityTypes ? "Loading activity types..." : "Select activity type"} />
</SelectTrigger>
<SelectContent>
{CLAIM_TYPES.map((type) => (
<SelectItem key={type} value={type}>{type}</SelectItem>
))}
{activityTypes.length > 0 ? (
activityTypes.map((type) => (
<SelectItem key={type.activityTypeId} value={type.title}>
{type.title}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-gray-500 text-center">
No activity types available
</div>
)}
</SelectContent>
</Select>
</div>

View File

@ -342,7 +342,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
setShowShareSummaryModal(true);
};
const isClosed = request?.status === 'closed' || (request?.status === 'approved' && !isInitiator) || (request?.status === 'rejected' && !isInitiator);
const isClosed = request?.status === 'closed';
// Fetch summary details if request is closed
useEffect(() => {

View File

@ -8,13 +8,15 @@ import {
Palette,
Lock,
Calendar,
Sliders
Sliders,
FileText
} from 'lucide-react';
import { setupPushNotifications } from '@/utils/pushNotifications';
import { useAuth, isAdmin as checkIsAdmin } from '@/contexts/AuthContext';
import { ConfigurationManager } from '@/components/admin/ConfigurationManager';
import { HolidayManager } from '@/components/admin/HolidayManager';
import { UserRoleManager } from '@/components/admin/UserRoleManager';
import { ActivityTypeManager } from '@/components/admin/ActivityTypeManager';
import { NotificationStatusModal } from '@/components/settings/NotificationStatusModal';
import { NotificationPreferencesModal } from '@/components/settings/NotificationPreferencesModal';
import { useState, useEffect } from 'react';
@ -30,6 +32,7 @@ export function Settings() {
const [showPreferencesModal, setShowPreferencesModal] = useState(false);
const [hasSubscription, setHasSubscription] = useState(false);
const [checkingSubscription, setCheckingSubscription] = useState(true);
const [showActivityTypeManager, setShowActivityTypeManager] = useState(false);
useEffect(() => {
checkSubscriptionStatus();
@ -161,7 +164,7 @@ export function Settings() {
{/* Tabs for Admin, Cards for Non-Admin */}
{isAdmin ? (
<Tabs defaultValue="user" className="w-full">
<TabsList className="grid w-full grid-cols-2 lg:grid-cols-4 mb-8 bg-slate-100 p-1 rounded-xl h-auto gap-1">
<TabsList className="grid w-full grid-cols-2 lg:grid-cols-5 mb-8 bg-slate-100 p-1 rounded-xl h-auto gap-1">
<TabsTrigger
value="user"
className="flex items-center justify-center gap-2 py-3 rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-md transition-all"
@ -194,6 +197,14 @@ export function Settings() {
<span className="hidden sm:inline">Holidays</span>
<span className="sm:hidden">Holidays</span>
</TabsTrigger>
<TabsTrigger
value="templates"
className="flex items-center justify-center gap-2 py-3 rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-md transition-all"
>
<FileText className="w-4 h-4" />
<span className="hidden sm:inline">Templates</span>
<span className="sm:hidden">Templates</span>
</TabsTrigger>
</TabsList>
{/* Fixed width container to prevent layout shifts */}
@ -330,6 +341,91 @@ export function Settings() {
<TabsContent value="holidays" className="mt-0">
<HolidayManager />
</TabsContent>
{/* Template Settings Tab (Admin Only) */}
<TabsContent value="templates" className="mt-0">
{!showActivityTypeManager ? (
<Card className="shadow-lg border-0 rounded-md">
<CardHeader className="border-b border-slate-100 py-4 sm:py-5">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-gradient-to-br from-slate-600 to-slate-700 rounded-md shadow-md">
<FileText className="w-5 h-5 text-white" />
</div>
<div>
<CardTitle className="text-lg sm:text-xl font-semibold text-slate-900">Template Settings</CardTitle>
<CardDescription className="text-sm">
Manage templates and activity types for workflows
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
<div className="space-y-4">
{/* Dealer Claim Activity Settings Card */}
<Card
className="shadow-md hover:shadow-lg transition-all duration-300 border border-slate-200 rounded-lg cursor-pointer group"
onClick={() => setShowActivityTypeManager(true)}
>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg shadow-md group-hover:shadow-lg transition-shadow">
<FileText className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-lg font-semibold text-slate-900 group-hover:text-blue-600 transition-colors">
Dealer Claim Activity Settings
</h3>
<p className="text-sm text-slate-600 mt-1">
Manage activity types for dealer claim workflows
</p>
</div>
</div>
<div className="text-slate-400 group-hover:text-blue-600 transition-colors">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
) : (
<div className="space-y-4">
<Card className="shadow-lg border-0 rounded-md">
<CardHeader className="border-b border-slate-100 py-4 sm:py-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="sm"
onClick={() => setShowActivityTypeManager(false)}
className="gap-2 hover:bg-slate-100"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Back
</Button>
<div className="p-2.5 bg-gradient-to-br from-slate-600 to-slate-700 rounded-md shadow-md">
<FileText className="w-5 h-5 text-white" />
</div>
<div>
<CardTitle className="text-lg sm:text-xl font-semibold text-slate-900">Dealer Claim Activity Settings</CardTitle>
<CardDescription className="text-sm">
Manage activity types for dealer claim workflows
</CardDescription>
</div>
</div>
</div>
</CardHeader>
</Card>
<ActivityTypeManager />
</div>
)}
</TabsContent>
</div>
</Tabs>
) : (

View File

@ -38,6 +38,15 @@ export interface Holiday {
appliesToLocations?: string[];
}
export interface ActivityType {
activityTypeId: string;
title: string;
itemCode?: string | null;
taxationType?: string | null;
sapRefNo?: string | null;
isActive?: boolean;
}
/**
* Get public configurations (accessible to all authenticated users)
* Returns non-sensitive configurations like DOCUMENT_POLICY, WORKFLOW_SHARING, TAT_SETTINGS
@ -125,3 +134,54 @@ export const bulkImportHolidays = async (holidays: Partial<Holiday>[]): Promise<
return response.data;
};
/**
* Get all activity types (public endpoint - no auth required)
*/
export const getActivityTypes = async (): Promise<ActivityType[]> => {
const response = await apiClient.get('/config/activity-types');
return response.data.data;
};
/**
* Get all activity types (admin only - includes inactive)
*/
export const getAllActivityTypes = async (activeOnly?: boolean): Promise<ActivityType[]> => {
const params = activeOnly !== undefined ? { activeOnly: activeOnly.toString() } : {};
const response = await apiClient.get('/admin/activity-types', { params });
return response.data.data;
};
/**
* Get a single activity type by ID (admin only)
*/
export const getActivityTypeById = async (activityTypeId: string): Promise<ActivityType> => {
const response = await apiClient.get(`/admin/activity-types/${activityTypeId}`);
return response.data.data;
};
/**
* Create a new activity type (admin only)
*/
export const createActivityType = async (activityType: Partial<ActivityType>): Promise<ActivityType> => {
const response = await apiClient.post('/admin/activity-types', activityType);
return response.data.data;
};
/**
* Update an activity type (admin only)
*/
export const updateActivityType = async (
activityTypeId: string,
updates: Partial<ActivityType>
): Promise<ActivityType> => {
const response = await apiClient.put(`/admin/activity-types/${activityTypeId}`, updates);
return response.data.data;
};
/**
* Delete (deactivate) an activity type (admin only)
*/
export const deleteActivityType = async (activityTypeId: string): Promise<void> => {
await apiClient.delete(`/admin/activity-types/${activityTypeId}`);
};