From 22d0fc70495691612ab2535c6962329f842746d6 Mon Sep 17 00:00:00 2001 From: shancds Date: Fri, 23 May 2025 09:34:02 +0530 Subject: [PATCH] feat(project-ratecard-member): add members to the project ratecard func (FE) --- .../ratecard-assignee-selector.tsx | 48 ++++----- .../reatecard-table/ratecard-table.tsx | 102 ++++++++---------- 2 files changed, 68 insertions(+), 82 deletions(-) diff --git a/worklenz-frontend/src/components/project-ratecard/ratecard-assignee-selector.tsx b/worklenz-frontend/src/components/project-ratecard/ratecard-assignee-selector.tsx index 96cf53e8..175ad48e 100644 --- a/worklenz-frontend/src/components/project-ratecard/ratecard-assignee-selector.tsx +++ b/worklenz-frontend/src/components/project-ratecard/ratecard-assignee-selector.tsx @@ -9,8 +9,7 @@ import Button from 'antd/es/button'; import Empty from 'antd/es/empty'; import { PlusOutlined } from '@ant-design/icons'; import SingleAvatar from '../common/single-avatar/single-avatar'; -import { JobRoleType } from '@/types/project/ratecard.types'; -import { IProjectMembersViewModel, IProjectMemberViewModel } from '@/types/projectMember.types'; +import { IProjectMemberViewModel } from '@/types/projectMember.types'; interface RateCardAssigneeSelectorProps { projectId: string; @@ -23,13 +22,17 @@ const RateCardAssigneeSelector = ({ projectId, onChange, selectedMemberIds = [], - memberlist, + memberlist = [], }: RateCardAssigneeSelectorProps) => { const membersInputRef = useRef(null); const [searchQuery, setSearchQuery] = useState(''); - const [members, setMembers] = useState(memberlist || []); - const [isLoading, setIsLoading] = useState(false); - const filteredMembers = members.filter(member => + const [members, setMembers] = useState(memberlist); + + useEffect(() => { + setMembers(memberlist); + }, [memberlist]); + + const filteredMembers = members.filter((member) => member.name?.toLowerCase().includes(searchQuery.toLowerCase()) ); @@ -38,45 +41,40 @@ const RateCardAssigneeSelector = ({ setSearchQuery(e.currentTarget.value)} + onChange={(e) => setSearchQuery(e.currentTarget.value)} placeholder="Search members" /> - + {filteredMembers.length ? ( - filteredMembers.map(member => ( + filteredMembers.map((member) => ( !member.pending_invitation && onChange?.(member.id || '')} > onChange?.(member.id || '')} /> -
- -
+ {member.name} - {/* {member.email} */}
)) ) : ( - + )}
@@ -87,7 +85,7 @@ const RateCardAssigneeSelector = ({ overlayClassName="custom-dropdown" trigger={['click']} dropdownRender={() => dropdownContent} - onOpenChange={open => { + onOpenChange={(open) => { if (open) setTimeout(() => membersInputRef.current?.focus(), 0); }} > @@ -101,4 +99,4 @@ const RateCardAssigneeSelector = ({ ); }; -export default RateCardAssigneeSelector; +export default RateCardAssigneeSelector; \ No newline at end of file diff --git a/worklenz-frontend/src/pages/projects/projectView/finance/ratecard-tab/reatecard-table/ratecard-table.tsx b/worklenz-frontend/src/pages/projects/projectView/finance/ratecard-tab/reatecard-table/ratecard-table.tsx index 2526cd4a..2e5bacca 100644 --- a/worklenz-frontend/src/pages/projects/projectView/finance/ratecard-tab/reatecard-table/ratecard-table.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/finance/ratecard-tab/reatecard-table/ratecard-table.tsx @@ -18,9 +18,7 @@ import RateCardAssigneeSelector from '@/components/project-ratecard/ratecard-ass import { projectsApiService } from '@/api/projects/projects.api.service'; import { IProjectMemberViewModel } from '@/types/projectMember.types'; - const RatecardTable: React.FC = () => { - const dispatch = useAppDispatch(); const { t } = useTranslation('project-view-finance'); const { projectId } = useParams(); @@ -33,16 +31,17 @@ const RatecardTable: React.FC = () => { // Local state for editing const [roles, setRoles] = useState(rolesRedux); const [addingRow, setAddingRow] = useState(false); - const [editingIndex, setEditingIndex] = useState(null); const [jobTitles, setJobTitles] = useState([]); const [members, setMembers] = useState([]); const [isLoadingMembers, setIsLoading] = useState(false); + const pagination = { current: 1, - pageSize: 100, + pageSize: 1000, field: 'name', order: 'asc', }; + const getProjectMembers = async () => { if (!projectId) return; setIsLoading(true); @@ -67,8 +66,7 @@ const RatecardTable: React.FC = () => { useEffect(() => { getProjectMembers(); - // eslint-disable-next-line - }, []); + }, [projectId]); // Fetch job titles for selection useEffect(() => { @@ -81,7 +79,7 @@ const RatecardTable: React.FC = () => { // Sync local roles with redux roles useEffect(() => { setRoles(rolesRedux); - }, [rolesRedux, dispatch]); + }, [rolesRedux]); // Fetch roles on mount useEffect(() => { @@ -98,7 +96,6 @@ const RatecardTable: React.FC = () => { // Save all roles (bulk update) const handleSaveAll = () => { if (projectId) { - // Only send roles with job_title_id and rate const filteredRoles = roles .filter((r) => r.job_title_id && typeof r.rate !== 'undefined') .map((r) => ({ @@ -114,14 +111,11 @@ const RatecardTable: React.FC = () => { const handleSelectJobTitle = async (jobTitleId: string) => { const jobTitle = jobTitles.find((jt) => jt.id === jobTitleId); if (!jobTitle || !projectId) return; - // Prevent duplicates if (roles.some((r) => r.job_title_id === jobTitleId)) return; - // 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([ @@ -131,6 +125,7 @@ const RatecardTable: React.FC = () => { job_title_id: newRole.job_title_id, jobtitle: newRole.jobtitle, rate: newRole.rate, + members: [], // Initialize members array }, ]); } @@ -150,18 +145,40 @@ const RatecardTable: React.FC = () => { if (record.id) { dispatch(deleteProjectRateCardRoleById(record.id)); } else { - // Remove unsaved row setRoles(roles.filter((_, idx) => idx !== index)); } }; + // Handle member change + const handleMemberChange = (memberId: string, rowIndex: number, record: JobRoleType) => { + if (!projectId && !memberId) return; + setRoles((prev) => + prev.map((role, idx) => { + if (idx !== rowIndex) return role; + const members = Array.isArray(role.members) ? [...role.members] : []; + const memberIdx = members.indexOf(memberId); + if (memberIdx > -1) { + members.splice(memberIdx, 1); // Remove if exists + } else { + members.push(memberId); // Add if not exists + } + return { ...role, members }; + }) + ); + // Log the required values + console.log({ + project_id: projectId, + id: memberId, + project_rate_card_role_id: record.id, + }); + }; + // Columns const columns: TableProps['columns'] = [ { title: t('jobTitleColumn'), dataIndex: 'jobtitle', render: (text: string, record: JobRoleType, index: number) => { - // Only show Select if addingRow and this is the last row (new row) if (addingRow && index === roles.length) { return ( ); } - return ( - setEditingIndex(index)} - > - {text || record.name} - - ); + return {text || record.name}; }, }, { @@ -222,16 +232,18 @@ const RatecardTable: React.FC = () => { render: (memberscol: string[] | null | undefined, record: JobRoleType, index: number) => (
- {memberscol && memberscol.length > 0 && - memberscol.map((member, i) => ( - - ))} + {memberscol?.map((memberId, i) => { + const member = members.find((m) => m.id === memberId); + return member ? ( + + ) : null; + })}
handleMemberChange(memberId, index)} + onChange={(memberId) => handleMemberChange(memberId, index, record)} memberlist={members} />
@@ -248,32 +260,12 @@ const RatecardTable: React.FC = () => { okText={t('yes')} cancelText={t('no')} > -