Files
worklenz/worklenz-frontend/src/components/projects/project-budget-settings-drawer/ProjectBudgetSettingsDrawer.tsx
chamikaJ 4ffc3465e3 feat(finance): implement project finance and rate card management features
- Added new controllers for managing project finance and rate cards, including CRUD operations for rate card roles and project finance tasks.
- Introduced API routes for project finance and rate card functionalities, enhancing the backend structure.
- Developed frontend components for displaying and managing project finance data, including a finance drawer and rate card settings.
- Enhanced localization files to support new UI elements and ensure consistency across multiple languages.
- Implemented utility functions for handling man-days and financial calculations, improving overall functionality.
2025-07-24 15:23:34 +05:30

269 lines
7.6 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Drawer,
Form,
Select,
InputNumber,
Button,
Space,
Typography,
Card,
Row,
Col,
Tooltip,
message,
Alert,
SettingOutlined,
InfoCircleOutlined,
DollarOutlined,
CalculatorOutlined,
SaveOutlined,
CloseOutlined,
} from '@/shared/antd-imports';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useAppSelector } from '@/hooks/useAppSelector';
import {
updateProjectFinanceCurrency,
fetchProjectFinancesSilent,
} from '@/features/projects/finance/project-finance.slice';
import { updateProjectCurrency, getProject } from '@/features/project/project.slice';
import { projectFinanceApiService } from '@/api/project-finance-ratecard/project-finance.api.service';
import { CURRENCY_OPTIONS } from '@/shared/currencies';
const { Text } = Typography;
interface ProjectBudgetSettingsDrawerProps {
visible: boolean;
onClose: () => void;
projectId: string;
}
const ProjectBudgetSettingsDrawer: React.FC<ProjectBudgetSettingsDrawerProps> = ({
visible,
onClose,
projectId,
}) => {
const { t } = useTranslation('project-view-finance');
const dispatch = useAppDispatch();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
// Get project data from Redux
const financeProject = useAppSelector(state => state.projectFinancesReducer.project);
const { project } = useAppSelector(state => state.projectReducer);
const activeGroup = useAppSelector(state => state.projectFinancesReducer.activeGroup);
const billableFilter = useAppSelector(state => state.projectFinancesReducer.billableFilter);
// Form initial values
const initialValues = {
budget: project?.budget || 0,
currency: financeProject?.currency || 'USD',
};
// Set form values when drawer opens
useEffect(() => {
if (visible && (project || financeProject)) {
form.setFieldsValue(initialValues);
setHasChanges(false);
}
}, [visible, project, financeProject, form]);
// Handle form value changes
const handleValuesChange = () => {
setHasChanges(true);
};
// Handle save
const handleSave = async () => {
try {
setLoading(true);
const values = await form.validateFields();
// Update budget if changed
if (values.budget !== project?.budget) {
await projectFinanceApiService.updateProjectBudget(projectId, values.budget);
}
// Update currency if changed
if (values.currency !== financeProject?.currency) {
await projectFinanceApiService.updateProjectCurrency(
projectId,
values.currency.toUpperCase()
);
dispatch(updateProjectCurrency(values.currency));
dispatch(updateProjectFinanceCurrency(values.currency));
}
message.success('Project settings updated successfully');
setHasChanges(false);
// Reload project finances after save
dispatch(
fetchProjectFinancesSilent({
projectId,
groupBy: activeGroup,
billableFilter,
resetExpansions: true,
})
);
// Also refresh the main project data to update budget statistics
dispatch(getProject(projectId));
onClose();
} catch (error) {
console.error('Failed to update project settings:', error);
message.error('Failed to update project settings');
} finally {
setLoading(false);
}
};
// Handle cancel
const handleCancel = () => {
if (hasChanges) {
form.setFieldsValue(initialValues);
setHasChanges(false);
}
onClose();
};
return (
<Drawer
title={
<Space>
<SettingOutlined />
<span>Project Budget Settings</span>
</Space>
}
width={480}
open={visible}
onClose={handleCancel}
footer={
<Space style={{ float: 'right' }}>
<Button icon={<CloseOutlined />} onClick={handleCancel}>
Cancel
</Button>
<Button
type="primary"
icon={<SaveOutlined />}
loading={loading}
disabled={!hasChanges}
onClick={handleSave}
>
Save Changes
</Button>
</Space>
}
>
<Form
form={form}
layout="vertical"
onValuesChange={handleValuesChange}
initialValues={initialValues}
>
{/* Budget Configuration */}
<Card
title={
<Space>
<DollarOutlined />
<span>Budget Configuration</span>
</Space>
}
size="small"
style={{ marginBottom: 16 }}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="budget"
label={
<Space>
<span>Project Budget</span>
<Tooltip title="Total budget allocated for this project">
<InfoCircleOutlined style={{ color: '#666' }} />
</Tooltip>
</Space>
}
>
<InputNumber
style={{ width: '100%' }}
min={0}
precision={2}
placeholder="Enter budget amount"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="currency" label="Currency">
<Select options={CURRENCY_OPTIONS} placeholder="Select currency" />
</Form.Item>
</Col>
</Row>
</Card>
{/* Calculation Method - Organization Wide Setting */}
<Card
title={
<Space>
<CalculatorOutlined />
<span>Cost Calculation Method</span>
</Space>
}
size="small"
style={{ marginBottom: 16 }}
>
<Space direction="vertical" style={{ width: '100%' }}>
<div>
<Text strong>Current Method: </Text>
<Text>
{financeProject?.calculation_method === 'man_days'
? `Man Days (${financeProject?.hours_per_day || 8}h/day)`
: 'Hourly Rates'}
</Text>
</div>
<Alert
message="Organization-wide Setting"
description={
<Space direction="vertical" size="small">
<Text>
The calculation method is now configured at the organization level and applies
to all projects.
</Text>
<Text>
To change this setting, please visit the{' '}
<strong>Admin Center Overview</strong> page.
</Text>
</Space>
}
type="info"
showIcon
/>
</Space>
</Card>
{/* Information Section */}
<Card title="Important Notes" size="small" type="inner">
<Space direction="vertical" size="small">
<Text type="secondary">
Changing the calculation method will affect how costs are calculated for all tasks
in this project
</Text>
<Text type="secondary">
Changes take effect immediately and will recalculate all project totals
</Text>
<Text type="secondary">
Budget settings apply to the entire project and all its tasks
</Text>
</Space>
</Card>
</Form>
</Drawer>
);
};
export default ProjectBudgetSettingsDrawer;