From d725e523b31dfea835f6e4ed4c5e6ab2cbcc869f Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Wed, 7 Jan 2026 18:56:16 +0530 Subject: [PATCH] activity type added in admi settings --- src/components/admin/ActivityTypeManager.tsx | 451 ++++++++++++++++++ src/components/admin/index.ts | 1 + src/custom/pages/RequestDetail.tsx | 2 +- .../ClaimManagementWizard.tsx | 61 ++- src/dealer-claim/pages/RequestDetail.tsx | 2 +- src/pages/Settings/Settings.tsx | 100 +++- src/services/adminApi.ts | 60 +++ 7 files changed, 651 insertions(+), 26 deletions(-) create mode 100644 src/components/admin/ActivityTypeManager.tsx diff --git a/src/components/admin/ActivityTypeManager.tsx b/src/components/admin/ActivityTypeManager.tsx new file mode 100644 index 0000000..02d8f98 --- /dev/null +++ b/src/components/admin/ActivityTypeManager.tsx @@ -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([]); + const [loading, setLoading] = useState(true); + const [showAddDialog, setShowAddDialog] = useState(false); + const [editingActivityType, setEditingActivityType] = useState(null); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(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 = { + 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 ( +
+ {/* Success Message */} + {successMessage && ( +
+
+ +
+

{successMessage}

+
+ )} + + {/* Error Message */} + {error && ( +
+
+ +
+

{error}

+ +
+ )} + + {/* Header */} + + +
+
+
+ +
+
+ Activity Types + + Manage dealer claim activity types + +
+
+ +
+
+
+ + {/* Activity Types List */} + {loading ? ( +
+ +
+ ) : activeActivityTypes.length === 0 ? ( + + +
+ +
+

No activity types found

+

Add activity types for dealer claim management

+ +
+
+ ) : ( +
+ {/* Active Activity Types */} + + +
+
+ Active Activity Types + + {activeActivityTypes.length} active type{activeActivityTypes.length !== 1 ? 's' : ''} + +
+
+ +
+
+
+ + {activeActivityTypes.map(activityType => ( +
+
+
+

{activityType.title}

+ + Active + +
+
+ {activityType.itemCode && ( + Item Code: {activityType.itemCode} + )} + {activityType.taxationType && ( + Taxation: {activityType.taxationType} + )} + {activityType.sapRefNo && ( + SAP Ref: {activityType.sapRefNo} + )} + {!activityType.itemCode && !activityType.taxationType && !activityType.sapRefNo && ( + No additional details + )} +
+
+
+ + +
+
+ ))} +
+
+ + {/* Inactive Activity Types */} + {inactiveActivityTypes.length > 0 && ( + + +
+
+ Inactive Activity Types + + {inactiveActivityTypes.length} inactive type{inactiveActivityTypes.length !== 1 ? 's' : ''} + +
+
+ +
+
+
+ + {inactiveActivityTypes.map(activityType => ( +
+
+
+

{activityType.title}

+ + Inactive + +
+
+
+ +
+
+ ))} +
+
+ )} +
+ )} + + {/* Add/Edit Dialog */} + + + +
+
+ +
+
+ + {editingActivityType ? 'Edit Activity Type' : 'Add New Activity Type'} + + + {editingActivityType ? 'Update activity type information' : 'Add a new activity type for dealer claim management'} + +
+
+
+ +
+ {/* Title Field */} +
+ + 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" + /> +

Enter the activity type title

+
+ + {/* Item Code Field */} +
+ + 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" + /> +

Optional item code for the activity type

+
+ + {/* Taxation Type Field */} +
+ + 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" + /> +

Optional taxation type for the activity

+
+ + {/* SAP Reference Number Field */} +
+ + 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" + /> +

Optional SAP reference number

+
+
+ + + + + +
+
+
+ ); +} + diff --git a/src/components/admin/index.ts b/src/components/admin/index.ts index 071315e..38d7fc2 100644 --- a/src/components/admin/index.ts +++ b/src/components/admin/index.ts @@ -1,3 +1,4 @@ export { ConfigurationManager } from './ConfigurationManager'; export { HolidayManager } from './HolidayManager'; +export { ActivityTypeManager } from './ActivityTypeManager'; diff --git a/src/custom/pages/RequestDetail.tsx b/src/custom/pages/RequestDetail.tsx index a24d505..3a04eab 100644 --- a/src/custom/pages/RequestDetail.tsx +++ b/src/custom/pages/RequestDetail.tsx @@ -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(() => { diff --git a/src/dealer-claim/components/request-creation/ClaimManagementWizard.tsx b/src/dealer-claim/components/request-creation/ClaimManagementWizard.tsx index 1520d7b..273fbe6 100644 --- a/src/dealer-claim/components/request-creation/ClaimManagementWizard.tsx +++ b/src/dealer-claim/components/request-creation/ClaimManagementWizard.tsx @@ -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([]); + 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
- updateFormData('activityType', value)} + disabled={loadingActivityTypes} + > - + - {CLAIM_TYPES.map((type) => ( - {type} - ))} + {activityTypes.length > 0 ? ( + activityTypes.map((type) => ( + + {type.title} + + )) + ) : ( +
+ No activity types available +
+ )}
diff --git a/src/dealer-claim/pages/RequestDetail.tsx b/src/dealer-claim/pages/RequestDetail.tsx index 678ca43..2d469bc 100644 --- a/src/dealer-claim/pages/RequestDetail.tsx +++ b/src/dealer-claim/pages/RequestDetail.tsx @@ -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(() => { diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index 8e50ab5..6953ade 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -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 ? ( - + Holidays Holidays + + + Templates + Templates + {/* Fixed width container to prevent layout shifts */} @@ -330,6 +341,91 @@ export function Settings() { + + {/* Template Settings Tab (Admin Only) */} + + {!showActivityTypeManager ? ( + + +
+
+ +
+
+ Template Settings + + Manage templates and activity types for workflows + +
+
+
+ +
+ {/* Dealer Claim Activity Settings Card */} + setShowActivityTypeManager(true)} + > + +
+
+
+ +
+
+

+ Dealer Claim Activity Settings +

+

+ Manage activity types for dealer claim workflows +

+
+
+
+ + + +
+
+
+
+
+
+
+ ) : ( +
+ + +
+
+ +
+ +
+
+ Dealer Claim Activity Settings + + Manage activity types for dealer claim workflows + +
+
+
+
+
+ +
+ )} +
) : ( diff --git a/src/services/adminApi.ts b/src/services/adminApi.ts index 2cce581..c8e4939 100644 --- a/src/services/adminApi.ts +++ b/src/services/adminApi.ts @@ -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[]): Promise< return response.data; }; +/** + * Get all activity types (public endpoint - no auth required) + */ +export const getActivityTypes = async (): Promise => { + 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 => { + 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 => { + 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): Promise => { + 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 +): Promise => { + 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 => { + await apiClient.delete(`/admin/activity-types/${activityTypeId}`); +}; +