Merge pull request #137 from shancds/feature/project-finance

Feature/project finance (project-ratecard-member-add)
This commit is contained in:
Chamika J
2025-05-26 08:33:29 +05:30
committed by GitHub
11 changed files with 457 additions and 131 deletions

View File

@@ -18,7 +18,7 @@ const initialState: financeState = {
isRatecardDrawerOpen: false,
isFinanceDrawerOpen: false,
isImportRatecardsDrawerOpen: false,
currency: 'LKR',
currency: 'USD',
isRatecardsLoading: false,
isFinanceDrawerloading: false,
drawerRatecard: null,

View File

@@ -25,7 +25,6 @@ export const fetchProjectRateCardRoles = createAsyncThunk(
async (project_id: string, { rejectWithValue }) => {
try {
const response = await projectRateCardApiService.getFromProjectId(project_id);
console.log('Project RateCard Roles:', response);
return response.body;
} catch (error) {
logger.error('Fetch Project RateCard Roles', error);
@@ -63,6 +62,23 @@ export const insertProjectRateCardRoles = createAsyncThunk(
}
);
export const insertProjectRateCardRole = createAsyncThunk(
'projectFinance/insertOne',
async (
{ project_id, job_title_id, rate }: { project_id: string; job_title_id: string; rate: number },
{ rejectWithValue }
) => {
try {
const response = await projectRateCardApiService.insertOne({ project_id, job_title_id, rate });
return response.body;
} catch (error) {
logger.error('Insert Project RateCard Role', error);
if (error instanceof Error) return rejectWithValue(error.message);
return rejectWithValue('Failed to insert project rate card role');
}
}
);
export const updateProjectRateCardRoleById = createAsyncThunk(
'projectFinance/updateById',
async ({ id, body }: { id: string; body: { job_title_id: string; rate: string } }, { rejectWithValue }) => {
@@ -105,6 +121,14 @@ export const deleteProjectRateCardRoleById = createAsyncThunk(
}
);
export const assignMemberToRateCardRole = createAsyncThunk(
'projectFinance/assignMemberToRateCardRole',
async ({ project_id, member_id, project_rate_card_role_id }: { project_id: string; member_id: string; project_rate_card_role_id: string }) => {
const response = await projectRateCardApiService.updateMemberRateCardRole(project_id, member_id, project_rate_card_role_id);
return response.body;
}
);
export const deleteProjectRateCardRolesByProjectId = createAsyncThunk(
'projectFinance/deleteByProjectId',
async (project_id: string, { rejectWithValue }) => {

View File

@@ -43,7 +43,7 @@ const RatecardDrawer = ({
const [isAddingRole, setIsAddingRole] = useState(false);
const [selectedJobTitleId, setSelectedJobTitleId] = useState<string | undefined>(undefined);
const [searchQuery, setSearchQuery] = useState('');
const [currency, setCurrency] = useState('LKR');
const [currency, setCurrency] = useState('USD');
const [name, setName] = useState<string>('Untitled Rate Card');
const [jobTitles, setJobTitles] = useState<IJobTitlesViewModel>({});
const [pagination, setPagination] = useState<PaginationType>({
@@ -95,7 +95,7 @@ const RatecardDrawer = ({
if (type === 'update' && drawerRatecard) {
setRoles(drawerRatecard.jobRolesList || []);
setName(drawerRatecard.name || '');
setCurrency(drawerRatecard.currency || 'LKR');
setCurrency(drawerRatecard.currency || 'USD');
}
}, [drawerRatecard, type]);
@@ -121,15 +121,17 @@ const RatecardDrawer = ({
setRoles(mergedRoles);
};
const handleAddRole = () => {
// Only allow adding if there are job titles not already in roles
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); // index of the new row
setAddingRowIndex(roles.length);
setIsAddingRole(true);
}
};
const handleDeleteRole = (index: number) => {
const updatedRoles = [...roles];
updatedRoles.splice(index, 1);
@@ -185,7 +187,7 @@ const RatecardDrawer = ({
} finally {
setRoles([]);
setName('Untitled Rate Card');
setCurrency('LKR');
setCurrency('USD');
}
}
};
@@ -218,6 +220,9 @@ const RatecardDrawer = ({
setAddingRowIndex(null);
}}
onBlur={() => {
if (roles[index].job_title_id === ""){
handleDeleteRole(index);
}
setEditingRowIndex(null);
setAddingRowIndex(null);
}}
@@ -239,7 +244,7 @@ const RatecardDrawer = ({
return (
<span
style={{ cursor: 'pointer' }}
onClick={() => setEditingRowIndex(index)}
// onClick={() => setEditingRowIndex(index)}
>
{record.jobtitle}
</span>
@@ -249,6 +254,7 @@ const RatecardDrawer = ({
{
title: `${t('ratePerHourColumn')} (${currency})`,
dataIndex: 'rate',
align: 'right',
render: (text: number, record: any, index: number) => (
<Input
type="number"
@@ -257,6 +263,7 @@ const RatecardDrawer = ({
background: 'transparent',
border: 'none',
boxShadow: 'none',
textAlign: 'right',
padding: 0,
}}
onChange={(e) => {
@@ -319,8 +326,8 @@ const RatecardDrawer = ({
<Select
value={currency}
options={[
{ value: 'LKR', label: 'LKR' },
{ value: 'USD', label: 'USD' },
{ value: 'LKR', label: 'LKR' },
{ value: 'INR', label: 'INR' },
]}
onChange={(value) => setCurrency(value)}
@@ -347,27 +354,7 @@ const RatecardDrawer = ({
rowKey={(record) => record.job_title_id}
pagination={false}
footer={() => (
isAddingRole ? (
<Select
showSearch
style={{ minWidth: 200 }}
placeholder={t('selectJobTitle')}
optionFilterProp="children"
value={selectedJobTitleId}
onChange={handleSelectJobTitle}
onBlur={() => setIsAddingRole(false)}
filterOption={(input, option) =>
(option?.children as string).toLowerCase().includes(input.toLowerCase())
}
>
{jobTitles.data?.map((jt) => (
<Select.Option key={jt.id} value={jt.id}>
{jt.name}
</Select.Option>
))}
</Select>
) : (
<Button
<Button
type="dashed"
onClick={handleAddRole}
block
@@ -375,7 +362,6 @@ const RatecardDrawer = ({
>
{t('addRoleButton')}
</Button>
)
)}
/>
</Drawer>