feat(ratecard): implement insertOne functionality for single role creation and update API integration
This commit is contained in:
@@ -6,6 +6,24 @@ import HandleExceptions from "../decorators/handle-exceptions";
|
||||
import WorklenzControllerBase from "./worklenz-controller-base";
|
||||
|
||||
export default class ProjectRateCardController extends WorklenzControllerBase {
|
||||
|
||||
// Insert a single role for a project
|
||||
@HandleExceptions()
|
||||
public static async insertOne(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||
const { project_id, job_title_id, rate } = req.body;
|
||||
if (!project_id || !job_title_id || typeof rate !== "number") {
|
||||
return res.status(400).send(new ServerResponse(false, null, "Invalid input"));
|
||||
}
|
||||
const q = `
|
||||
INSERT INTO finance_project_rate_card_roles (project_id, job_title_id, rate)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (project_id, job_title_id) DO UPDATE SET rate = EXCLUDED.rate
|
||||
RETURNING *,
|
||||
(SELECT name FROM job_titles jt WHERE jt.id = finance_project_rate_card_roles.job_title_id) AS jobtitle;
|
||||
`;
|
||||
const result = await db.query(q, [project_id, job_title_id, rate]);
|
||||
return res.status(200).send(new ServerResponse(true, result.rows[0]));
|
||||
}
|
||||
// Insert multiple roles for a project
|
||||
@HandleExceptions()
|
||||
public static async insertMany(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||
@@ -81,12 +99,11 @@ export default class ProjectRateCardController extends WorklenzControllerBase {
|
||||
if (!Array.isArray(roles) || !project_id) {
|
||||
return res.status(400).send(new ServerResponse(false, null, "Invalid input"));
|
||||
}
|
||||
// Delete existing
|
||||
await db.query(`DELETE FROM finance_project_rate_card_roles WHERE project_id = $1`, [project_id]);
|
||||
// Insert new
|
||||
if (roles.length === 0) {
|
||||
// If no roles provided, do nothing and return empty array
|
||||
return res.status(200).send(new ServerResponse(true, []));
|
||||
}
|
||||
// Build upsert query for all roles
|
||||
const values = roles.map((role: any) => [
|
||||
project_id,
|
||||
role.job_title_id,
|
||||
@@ -95,8 +112,9 @@ export default class ProjectRateCardController extends WorklenzControllerBase {
|
||||
const q = `
|
||||
INSERT INTO finance_project_rate_card_roles (project_id, job_title_id, rate)
|
||||
VALUES ${values.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`).join(",")}
|
||||
ON CONFLICT (project_id, job_title_id) DO UPDATE SET rate = EXCLUDED.rate, updated_at = NOW()
|
||||
RETURNING *,
|
||||
(SELECT name FROM job_titles jt WHERE jt.id = finance_project_rate_card_roles.job_title_id) AS jobtitle;
|
||||
(SELECT name FROM job_titles jt WHERE jt.id = finance_project_rate_card_roles.job_title_id) AS jobtitle;
|
||||
`;
|
||||
const flatValues = values.flat();
|
||||
const result = await db.query(q, flatValues);
|
||||
|
||||
@@ -12,6 +12,12 @@ projectRatecardApiRouter.post(
|
||||
projectManagerValidator,
|
||||
safeControllerFunction(ProjectRateCardController.insertMany)
|
||||
);
|
||||
// Insert a single role for a project
|
||||
projectRatecardApiRouter.post(
|
||||
"/create-project-rate-card-role",
|
||||
projectManagerValidator,
|
||||
safeControllerFunction(ProjectRateCardController.insertOne)
|
||||
);
|
||||
|
||||
// Get all roles for a project
|
||||
projectRatecardApiRouter.get(
|
||||
|
||||
@@ -21,6 +21,14 @@ export const projectRateCardApiService = {
|
||||
const response = await apiClient.post<IServerResponse<IProjectRateCardRole[]>>(rootUrl, { project_id, roles });
|
||||
return response.data;
|
||||
},
|
||||
// Insert a single role for a project
|
||||
async insertOne({ project_id, job_title_id, rate }: { project_id: string; job_title_id: string; rate: number }): Promise<IServerResponse<IProjectRateCardRole>> {
|
||||
const response = await apiClient.post<IServerResponse<IProjectRateCardRole>>(
|
||||
`${rootUrl}/create-project-rate-card-role`,
|
||||
{ project_id, job_title_id, rate }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get all roles for a project
|
||||
async getFromProjectId(project_id: string): Promise<IServerResponse<IProjectRateCardRole[]>> {
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -253,6 +253,7 @@ const RatecardDrawer = ({
|
||||
render: (text: number, record: any, index: number) => (
|
||||
<Input
|
||||
type="number"
|
||||
autoFocus={index === addingRowIndex}
|
||||
value={roles[index]?.rate ?? 0}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { JobRoleType, IJobType, RatecardType } from '@/types/project/ratecard.ty
|
||||
import {
|
||||
deleteProjectRateCardRoleById,
|
||||
fetchProjectRateCardRoles,
|
||||
insertProjectRateCardRole,
|
||||
updateProjectRateCardRolesByProjectId,
|
||||
} from '@/features/finance/project-finance-slice';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -40,9 +41,8 @@ const RatecardTable: React.FC = () => {
|
||||
|
||||
// Sync local roles with redux roles
|
||||
useEffect(() => {
|
||||
console.log('Roles Redux:', rolesRedux);
|
||||
setRoles(rolesRedux);
|
||||
}, [rolesRedux]);
|
||||
}, [rolesRedux, dispatch]);
|
||||
|
||||
// Fetch roles on mount
|
||||
useEffect(() => {
|
||||
@@ -72,20 +72,29 @@ const RatecardTable: React.FC = () => {
|
||||
};
|
||||
|
||||
// Handle job title select for new row
|
||||
const handleSelectJobTitle = (jobTitleId: string) => {
|
||||
const handleSelectJobTitle = async(jobTitleId: string) => {
|
||||
const jobTitle = jobTitles.find((jt) => jt.id === jobTitleId);
|
||||
if (!jobTitle) return;
|
||||
if (!jobTitle || !projectId) return;
|
||||
// Prevent duplicates
|
||||
if (roles.some((r) => r.job_title_id === jobTitleId)) return;
|
||||
setRoles([
|
||||
...roles,
|
||||
{
|
||||
job_title_id: jobTitleId,
|
||||
jobtitle: jobTitle.name || '',
|
||||
rate: 0,
|
||||
members: [],
|
||||
},
|
||||
]);
|
||||
// Dispatch and wait for result
|
||||
const resultAction = await dispatch(
|
||||
insertProjectRateCardRole({ project_id: projectId, job_title_id: jobTitleId, rate: 0 })
|
||||
);
|
||||
|
||||
// If fulfilled, update local state with returned id
|
||||
if (insertProjectRateCardRole.fulfilled.match(resultAction)) {
|
||||
const newRole = resultAction.payload;
|
||||
setRoles([
|
||||
...roles,
|
||||
{
|
||||
id: newRole.id,
|
||||
job_title_id: newRole.job_title_id,
|
||||
jobtitle: newRole.jobtitle,
|
||||
rate: newRole.rate,
|
||||
},
|
||||
]);
|
||||
}
|
||||
setAddingRow(false);
|
||||
};
|
||||
|
||||
@@ -221,14 +230,14 @@ const RatecardTable: React.FC = () => {
|
||||
dataSource={
|
||||
addingRow
|
||||
? [
|
||||
...roles,
|
||||
{
|
||||
job_title_id: '',
|
||||
jobtitle: '',
|
||||
rate: 0,
|
||||
members: [],
|
||||
},
|
||||
]
|
||||
...roles,
|
||||
{
|
||||
job_title_id: '',
|
||||
jobtitle: '',
|
||||
rate: 0,
|
||||
members: [],
|
||||
},
|
||||
]
|
||||
: roles
|
||||
}
|
||||
columns={columns}
|
||||
|
||||
Reference in New Issue
Block a user