import { Drawer, Select, Typography, Flex, Button, Input, Table } from 'antd'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppSelector } from '../../../hooks/useAppSelector'; import { useAppDispatch } from '../../../hooks/useAppDispatch'; import { deleteRateCard, fetchRateCardById, fetchRateCards, toggleRatecardDrawer, updateRateCard } from '../finance-slice'; import { RatecardType, IJobType } from '@/types/project/ratecard.types'; import { IJobTitlesViewModel } from '@/types/job.types'; import { jobTitlesApiService } from '@/api/settings/job-titles/job-titles.api.service'; import { DeleteOutlined } from '@ant-design/icons'; interface PaginationType { current: number; pageSize: number; field: string; order: string; total: number; pageSizeOptions: string[]; size: 'small' | 'default'; } const RatecardDrawer = ({ type, ratecardId, onSaved, }: { type: 'create' | 'update'; ratecardId: string; onSaved?: () => void; }) => { const [ratecardsList, setRatecardsList] = useState([]); // initial Job Roles List (dummy data) const [roles, setRoles] = useState([]); const [addingRowIndex, setAddingRowIndex] = useState(null); const { t } = useTranslation('settings/ratecard-settings'); // get drawer state from client reducer const drawerLoading = useAppSelector(state => state.financeReducer.isFinanceDrawerloading); const drawerRatecard = useAppSelector(state => state.financeReducer.drawerRatecard); const isDrawerOpen = useAppSelector( (state) => state.financeReducer.isRatecardDrawerOpen ); const dispatch = useAppDispatch(); const [isAddingRole, setIsAddingRole] = useState(false); const [selectedJobTitleId, setSelectedJobTitleId] = useState(undefined); const [searchQuery, setSearchQuery] = useState(''); const [currency, setCurrency] = useState('USD'); const [name, setName] = useState('Untitled Rate Card'); const [jobTitles, setJobTitles] = useState({}); const [pagination, setPagination] = useState({ current: 1, pageSize: 10000, field: 'name', order: 'desc', total: 0, pageSizeOptions: ['5', '10', '15', '20', '50', '100'], size: 'small', }); const [editingRowIndex, setEditingRowIndex] = useState(null); const getJobTitles = useMemo(() => { return async () => { const response = await jobTitlesApiService.getJobTitles( pagination.current, pagination.pageSize, pagination.field, pagination.order, searchQuery ); if (response.done) { setJobTitles(response.body); setPagination(prev => ({ ...prev, total: response.body.total || 0 })); } }; }, [pagination.current, pagination.pageSize, pagination.field, pagination.order, searchQuery]); // fetch rate cards data useEffect(() => { getJobTitles(); }, []); // get currently selected ratecard const selectedRatecard = ratecardsList.find( (ratecard) => ratecard.id === ratecardId ); useEffect(() => { if (type === 'update' && ratecardId) { dispatch(fetchRateCardById(ratecardId)); } // ...reset logic for create... }, [type, ratecardId, dispatch]); useEffect(() => { if (type === 'update' && drawerRatecard) { setRoles(drawerRatecard.jobRolesList || []); setName(drawerRatecard.name || ''); setCurrency(drawerRatecard.currency || 'USD'); } }, [drawerRatecard, type]); // Add All handler const handleAddAllRoles = () => { if (!jobTitles.data) return; // Get current job_title_ids in roles const existingIds = new Set(roles.map(r => r.job_title_id)); // Only add job titles not already present const newRoles = jobTitles.data .filter(jt => jt.id && !existingIds.has(jt.id)) .map(jt => ({ jobtitle: jt.name, rate_card_id: ratecardId, job_title_id: jt.id!, rate: 0, })); // Prevent any accidental duplicates by merging and filtering again const mergedRoles = [...roles, ...newRoles].filter( (role, idx, arr) => arr.findIndex(r => r.job_title_id === role.job_title_id) === idx ); setRoles(mergedRoles); }; const handleAddRole = () => { const existingIds = new Set(roles.map(r => r.job_title_id)); const availableJobTitles = jobTitles.data?.filter(jt => !existingIds.has(jt.id!)); if (availableJobTitles && availableJobTitles.length > 0) { setRoles([...roles, { job_title_id: '', rate: 0 }]); setAddingRowIndex(roles.length); setIsAddingRole(true); } }; const handleDeleteRole = (index: number) => { const updatedRoles = [...roles]; updatedRoles.splice(index, 1); setRoles(updatedRoles); }; const handleSelectJobTitle = (jobTitleId: string) => { // Prevent duplicate job_title_id if (roles.some(role => role.job_title_id === jobTitleId)) { setIsAddingRole(false); setSelectedJobTitleId(undefined); return; } const jobTitle = jobTitles.data?.find(jt => jt.id === jobTitleId); if (jobTitle) { const newRole = { jobtitle: jobTitle.name, rate_card_id: ratecardId, job_title_id: jobTitleId, rate: 0, }; setRoles([...roles, newRole]); } setIsAddingRole(false); setSelectedJobTitleId(undefined); }; const handleSave = async () => { if (type === 'update' && ratecardId) { try { // Filter out roles with no jobtitle or empty jobtitle const filteredRoles = roles.filter(role => role.jobtitle && role.jobtitle.trim() !== ''); await dispatch(updateRateCard({ id: ratecardId, body: { name, currency, jobRolesList: filteredRoles, }, }) as any); // Refresh the rate cards list in Redux await dispatch(fetchRateCards({ index: 1, size: 10, field: 'name', order: 'desc', search: '', }) as any); if (onSaved) onSaved(); dispatch(toggleRatecardDrawer()); } catch (error) { console.error('Failed to update rate card', error); } finally { setRoles([]); setName('Untitled Rate Card'); setCurrency('USD'); } } }; // table columns const columns = [ { title: t('jobTitleColumn'), dataIndex: 'jobtitle', render: (text: string, record: any, index: number) => { if (index === addingRowIndex || index === editingRowIndex) { return ( ); } // Render as clickable text for existing rows return ( setEditingRowIndex(index)} > {record.jobtitle} ); }, }, { title: `${t('ratePerHourColumn')} (${currency})`, dataIndex: 'rate', align: 'right', render: (text: number, record: any, index: number) => ( { const updatedRoles = roles.map((role, idx) => idx === index ? { ...role, rate: parseInt(e.target.value, 10) || 0 } : role ); setRoles(updatedRoles); }} /> ), }, { title: t('actionsColumn') || 'Actions', dataIndex: 'actions', render: (_: any, __: any, index: number) => (