From ff91ac85ffca04f30880c5b8aa4d343bee70f9d5 Mon Sep 17 00:00:00 2001 From: FlashL3opard <69573060+Flashl3opard@users.noreply.github.com> Date: Wed, 13 May 2026 15:38:05 +0530 Subject: [PATCH] MXWAR-88: Fix users create/edit flow and breadcrumb --- src/pages/users/CreateUsers.tsx | 162 +++++++++++++++++++++++++++++--- src/pages/users/EditUsers.tsx | 121 +++++++++++++++++++----- src/pages/users/ViewUsers.tsx | 40 +++++++- 3 files changed, 281 insertions(+), 42 deletions(-) diff --git a/src/pages/users/CreateUsers.tsx b/src/pages/users/CreateUsers.tsx index d4cc349..f3b23c1 100644 --- a/src/pages/users/CreateUsers.tsx +++ b/src/pages/users/CreateUsers.tsx @@ -7,6 +7,7 @@ */ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' +import type { AxiosError } from 'axios' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' @@ -21,12 +22,41 @@ import { UsersApi, type GetUsersTemplateResponse, type StaffData, + type PostUsersRequest, } from '@/fineract-api' import { getConfiguration } from '@/lib/fineract-openapi' const userApi = new UsersApi(getConfiguration()) const staffApi = new StaffApi(getConfiguration()) +type UserApiErrorResponse = { + defaultUserMessage?: string + developerMessage?: string + errors?: Array<{ + defaultUserMessage?: string + developerMessage?: string + }> +} + +const parseId = (value: string) => { + const parsed = parseInt(value, 10) + return Number.isNaN(parsed) ? undefined : parsed +} + +const getUserErrorMessage = (error: unknown) => { + const axiosError = error as AxiosError + const responseData = axiosError.response?.data + + return ( + responseData?.errors?.[0]?.defaultUserMessage || + responseData?.errors?.[0]?.developerMessage || + responseData?.defaultUserMessage || + responseData?.developerMessage || + axiosError.message || + 'Failed to create user' + ) +} + const CreateUsers = () => { const [users, setUsers] = useState() const [staff, setStaff] = useState(null) @@ -37,7 +67,7 @@ const CreateUsers = () => { firstName: '', lastName: '', passwordNeverExpiers: false, - sendPasswordToEmail: true, + sendPasswordToEmail: false, office: '', staff: '', roles: '', @@ -83,7 +113,73 @@ const CreateUsers = () => { fetchStaff() }, [formData.office]) + const staffOptions = (staff || []).filter( + option => option.officeId?.toString() === formData.office + ) + const navigate = useNavigate() + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + const requiresStaff = staffOptions.length > 0 + + if ( + !formData.username.trim() || + !formData.email.trim() || + !formData.firstName.trim() || + !formData.lastName.trim() || + !formData.office || + !formData.roles || + (requiresStaff && !formData.staff) + ) { + alert('Please fill all required fields.') + return + } + + if (showPassword) { + if (!formData.password || !formData.repeatPassword) { + alert('Please enter and confirm the password.') + return + } + if (passwordShort || passwordsDontMatch) { + alert('Please fix the password errors.') + return + } + } + + const officeId = parseId(formData.office) + const roleId = parseId(formData.roles) + const staffId = formData.staff ? parseId(formData.staff) : undefined + + if (!officeId || !roleId || (formData.staff && !staffId)) { + alert('Please select valid office, role, and staff values.') + return + } + + const payload: PostUsersRequest = { + username: formData.username.trim(), + email: formData.email.trim(), + firstname: formData.firstName.trim(), + lastname: formData.lastName.trim(), + officeId, + roles: [roleId], + staffId, + passwordNeverExpires: formData.passwordNeverExpiers, + sendPasswordToEmail: formData.sendPasswordToEmail, + password: showPassword ? formData.password : undefined, + repeatPassword: showPassword ? formData.repeatPassword : undefined, + } + + try { + await userApi.create15(payload) + alert('User created successfully!') + navigate('/appusers') + } catch (err) { + console.error('Failed to create user', err) + alert(getUserErrorMessage(err)) + } + } return (
{

Create User

-
+
- + + setFormData(prev => ({ + ...prev, + username: e.target.value, + })) + } + />
- + + setFormData(prev => ({ + ...prev, + email: e.target.value, + })) + } + />
- + + setFormData(prev => ({ + ...prev, + firstName: e.target.value, + })) + } + />
- + + setFormData(prev => ({ + ...prev, + lastName: e.target.value, + })) + } + />
@@ -200,7 +336,7 @@ const CreateUsers = () => { selectLabel="Office *" selectValue={formData.office} selectOnChange={value => - setFormData(prev => ({ ...prev, office: value })) + setFormData(prev => ({ ...prev, office: value, staff: '' })) } selectPlaceholder="Select office" selectOptions={(users?.allowedOffices || []) @@ -217,14 +353,10 @@ const CreateUsers = () => { setFormData(prev => ({ ...prev, staff: value })) } selectPlaceholder="Select staff" - selectOptions={(staff || []) - .filter( - option => option.officeId?.toString() === formData.office - ) - .map(option => ({ - id: option.id!, - name: option.displayName!, - }))} + selectOptions={staffOptions.map(option => ({ + id: option.id!, + name: option.displayName!, + }))} />
diff --git a/src/pages/users/EditUsers.tsx b/src/pages/users/EditUsers.tsx index acc0557..1148ec7 100644 --- a/src/pages/users/EditUsers.tsx +++ b/src/pages/users/EditUsers.tsx @@ -7,6 +7,7 @@ */ import { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' +import type { AxiosError } from 'axios' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' @@ -22,12 +23,41 @@ import { type GetUsersResponse, type GetUsersTemplateResponse, type StaffData, + type PutUsersUserIdRequest, } from '@/fineract-api' import { getConfiguration } from '@/lib/fineract-openapi' const userApi = new UsersApi(getConfiguration()) const staffApi = new StaffApi(getConfiguration()) +type UserApiErrorResponse = { + defaultUserMessage?: string + developerMessage?: string + errors?: Array<{ + defaultUserMessage?: string + developerMessage?: string + }> +} + +const parseId = (value: string) => { + const parsed = parseInt(value, 10) + return Number.isNaN(parsed) ? undefined : parsed +} + +const getUserErrorMessage = (error: unknown) => { + const axiosError = error as AxiosError + const responseData = axiosError.response?.data + + return ( + responseData?.errors?.[0]?.defaultUserMessage || + responseData?.errors?.[0]?.developerMessage || + responseData?.defaultUserMessage || + responseData?.developerMessage || + axiosError.message || + 'Failed to update user' + ) +} + const EditUsers = () => { const [users, setUsers] = useState() const [staff, setStaff] = useState(null) @@ -64,6 +94,10 @@ const EditUsers = () => { .catch(console.error) }, [formData.office]) + const staffOptions = (staff || []).filter( + option => option.officeId?.toString() === formData.office + ) + // fetch user details for editing useEffect(() => { if (!id) return @@ -79,6 +113,7 @@ const EditUsers = () => { firstName: data.firstname || '', lastName: data.lastname || '', office: data.officeId?.toString() || '', + staff: data.staff?.toString() || '', roles: data.selectedRoles?.[0]?.id?.toString() || '', passwordNeverExpiers: data.passwordNeverExpires || false, })) @@ -89,29 +124,49 @@ const EditUsers = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() + if (!id) return + + const requiresStaff = staffOptions.length > 0 + // basic validation if ( - !formData.username || - !formData.email || - !formData.firstName || - !formData.lastName || - formData.office || - formData.roles + !formData.username.trim() || + !formData.email.trim() || + !formData.firstName.trim() || + !formData.lastName.trim() || + !formData.office || + !formData.roles || + (requiresStaff && !formData.staff) ) { alert('Please fill all required fields.') return } - const _payload = { - // Reserved for future use - username: formData.username, - email: formData.email, - firstname: formData.firstName, - lastname: formData.lastName, - passwordNeverExpiers: formData.passwordNeverExpiers, - office: formData.office, - staff: formData.staff, - roles: formData.roles, + const officeId = parseId(formData.office) + const roleId = parseId(formData.roles) + const staffId = formData.staff ? parseId(formData.staff) : undefined + + if (!officeId || !roleId || (formData.staff && !staffId)) { + alert('Please select valid office, role, and staff values.') + return + } + + const payload: PutUsersUserIdRequest = { + email: formData.email.trim(), + firstname: formData.firstName.trim(), + lastname: formData.lastName.trim(), + officeId, + roles: [roleId], + staffId, + } + + try { + await userApi.update26(Number(id), payload) + alert('User updated successfully!') + navigate('/appusers') + } catch (err) { + console.error('Failed to update user', err) + alert(getUserErrorMessage(err)) } } @@ -135,6 +190,7 @@ const EditUsers = () => { placeholder="Enter username" className="w-full" value={formData.username} + disabled />
@@ -143,6 +199,12 @@ const EditUsers = () => { placeholder="Enter email" className="w-full" value={formData.email} + onChange={e => + setFormData(prev => ({ + ...prev, + email: e.target.value, + })) + } />
@@ -154,6 +216,12 @@ const EditUsers = () => { placeholder="Enter First Name" className="w-full" value={formData.firstName} + onChange={e => + setFormData(prev => ({ + ...prev, + firstName: e.target.value, + })) + } />
@@ -162,6 +230,12 @@ const EditUsers = () => { placeholder="Enter Last Name" className="w-full" value={formData.lastName} + onChange={e => + setFormData(prev => ({ + ...prev, + lastName: e.target.value, + })) + } />
@@ -171,6 +245,7 @@ const EditUsers = () => { @@ -181,7 +256,7 @@ const EditUsers = () => { selectLabel="Office *" selectValue={formData.office} selectOnChange={value => - setFormData(prev => ({ ...prev, office: value })) + setFormData(prev => ({ ...prev, office: value, staff: '' })) } selectPlaceholder="Select office" selectOptions={(users?.allowedOffices || []) @@ -199,14 +274,10 @@ const EditUsers = () => { setFormData(prev => ({ ...prev, staff: value })) } selectPlaceholder="Select staff" - selectOptions={(staff || []) - .filter( - option => option.officeId?.toString() === formData.office - ) - .map(option => ({ - id: option.id!, - name: option.displayName!, - }))} + selectOptions={staffOptions.map(option => ({ + id: option.id!, + name: option.displayName!, + }))} /> diff --git a/src/pages/users/ViewUsers.tsx b/src/pages/users/ViewUsers.tsx index 2c70f3a..c9ea43c 100644 --- a/src/pages/users/ViewUsers.tsx +++ b/src/pages/users/ViewUsers.tsx @@ -7,6 +7,7 @@ */ import { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' +import type { AxiosError } from 'axios' import { AlertDialog, @@ -39,6 +40,29 @@ import { const usersApi = new UsersApi(getConfiguration()) // API +type UserApiErrorResponse = { + defaultUserMessage?: string + developerMessage?: string + errors?: Array<{ + defaultUserMessage?: string + developerMessage?: string + }> +} + +const getUserErrorMessage = (error: unknown) => { + const axiosError = error as AxiosError + const responseData = axiosError.response?.data + + return ( + responseData?.errors?.[0]?.defaultUserMessage || + responseData?.errors?.[0]?.developerMessage || + responseData?.defaultUserMessage || + responseData?.developerMessage || + axiosError.message || + 'Failed to delete user' + ) +} + const ViewUsers = () => { const navigate = useNavigate() const { id } = useParams() // route param @@ -57,6 +81,18 @@ const ViewUsers = () => { fetchViewUsers() }, [id]) + const handleDelete = async () => { + if (!users?.id) return + try { + await usersApi.delete23(Number(users.id)) + alert('User deleted successfully!') + navigate('/appusers') + } catch (err) { + console.error('Failed to delete user', err) + alert(getUserErrorMessage(err)) + } + } + return (
{/* breadcrumbs */} @@ -64,7 +100,7 @@ const ViewUsers = () => { items={[ { label: 'Home', href: '/home' }, { label: 'Accounting' }, - { label: 'Users', href: '/users' }, + { label: 'Users', href: '/appusers' }, { label: `${users?.id}`, current: true }, ]} /> @@ -102,7 +138,7 @@ const ViewUsers = () => { Confirm