Files
revenue-fe/src/pages/members/manage-members/blocks/DetailMember.tsx
2025-06-04 11:13:34 +07:00

433 lines
23 KiB
TypeScript

import { apiConfig } from '@/config/api.config';
import { Alert, useDataGrid } from '@/components';
import axios from 'axios';
import { useAuthContext } from '@/auth';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
import {
Dialog,
DialogBody,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { initialMember } from "../Columns";
import AdmAccess from './AdmAccess';
import CustomerWallet from './CustomerWallet';
const BASE_URL_MASTER_DATA = apiConfig.service_master_data;
const URL_NATIONALITY = apiConfig.nationality;
const BASE_URL_CUSTOMER = apiConfig.service_customer;
const DetailMember = ({ showAddDialog, setShowAddDialog, handleSubmit, initialData, handleReject, page, fetchCustomers,
handleClose, profession, dialogType
}: any) => {
const [formData, setFormData] = useState(initialData || initialMember);
const [viewOnly, setViewOnly] = useState(false);
const [nationality, setNationality] = useState([]);
const [municipios, setMunicipios] = useState([]);
const [aldeias, setAldeias] = useState([]);
const [postoAdm, setPostoAdm] = useState([]);
const [sucos, setSucos] = useState<any>([]);
const [groups, setGroups] = useState([]);
const [isAdmin, setisAdmin] = useState(false);
const [identity_type] = useState([
{ id: 'Eleitoral ID', name: 'Eleitoral ID' },
{ id: 'Bihete de Identidade', name: 'Bihete de Identidade' },
{ id: 'Passport', name: 'Passport' },
]);
const [genders] = useState([ { name: 'Male',id: 'M' }, { name: 'Female',id: 'F' }])
const [previewImg, setPreviewImg] = useState({
status: false,
img: ''
});
const [banks] = useState([
{ name: 'BNCTL',id: 'BNCTL' }, { name: 'BRI',id: 'BRI' }, { name: 'BNU',id: 'BNU' }, { name: 'MANDIRI',id: 'MANDIRI' }
])
const parentRef = useRef<any | null>(null);
const [alert, setAlert] = useState({
show: false,
message: ''
});
const { getUser } = useAuthContext();
useEffect(() => {
setFormData(initialData || {});
fetchMasterData()
}, [initialData]);
const handleChange = async (e: any) => {
const { name, value } = e.target;
if (name === "file_selfie" || name === "photouser" || name === 'file_document_id' || name === "file_document_id_selfie" ||
name === "file_commercial_license"|| name === 'photomerchant') { // FOR FILE ONLY
setFormData({ ...formData, [name]: e.target.files[0] });
} else if(name === "nationality") {
setFormData({ ...formData, [name]: value });
let getNationality = await axios.get(`${URL_NATIONALITY}/${value}`);
setNationality(getNationality.data.data)
} else {
if (name === 'municipio_id' || name === 'posto_adms_id' || name === 'suco_id') await getMasterAfter(name, value);
if (name==='msisdn') setFormData({ ...formData, [name]: value.replace(/\D/g, '') })
else setFormData({ ...formData, [name]: value });
}
};
async function getMasterAfter(name: string, id: any) {
if (name === 'municipio_id') {
let getMunicipiosPosto = await axios.get(`${BASE_URL_MASTER_DATA}/municipios/postoadms/${id}`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setPostoAdm(getMunicipiosPosto.data.data)
}
if (name === 'posto_adms_id') {
let getPostoSuco = await axios.get(`${BASE_URL_MASTER_DATA}/postoadms/sucos/${id}`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setSucos(getPostoSuco.data.data)
}
if (name === 'suco_id') {
let getSucoAldeias = await axios.get(`${BASE_URL_MASTER_DATA}/sucos/aldeias/${id}`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setAldeias(getSucoAldeias.data.data)
}
}
const handleAddDialog = (show:boolean) => {
if (show) {
setShowAddDialog(show)
} else {
handleClose()
setShowAddDialog(show)
}
}
async function fetchMasterData() {
try {
let userlogin:any = await getUser();
let userloginrole = userlogin?.data?.role?.name;
if (userloginrole === 'Admin') {
setisAdmin(true);
setViewOnly(true)
}
let getGroups = await axios.get(`${BASE_URL_CUSTOMER}/groups/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC'
}
});
setGroups(getGroups.data.data.list);
let getMunicipios = await axios.get(`${BASE_URL_MASTER_DATA}/municipios/list`, {
params: {
limit: 70,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setMunicipios(getMunicipios.data.data.list)
if (formData.municipio_id) await handleChange({target: { name: "municipio_id",value: formData.municipio_id }});
if (formData.posto_adms_id) await handleChange({target: { name: "posto_adms_id",value: formData.posto_adms_id }});
if (formData.suco_id) await handleChange({target: { name: "suco_id",value: formData.suco_id }});
if (formData.aldeia_id) await handleChange({target: { name: "aldeia_id",value: formData.aldeia_id }});
} catch (error:any) {
console.log(error);
toast.error(error.message)
}
}
function buttonOnSubmit(e:any) {
e.preventDefault();
const requiredFields:any = {
msisdn: "msisdn",
email: "email",
fullname: "fullname",
username: "username",
address: "Address",
nationality: "Nationality",
date_birth: "Date Of Birth",
gender: "Gender"
};
const missingFields = Object.keys(requiredFields).filter((key) => !formData[key]);
if (missingFields.length > 0) {
const missingNames = missingFields.map((key:any) => requiredFields[key]).join(", ");
return toast.warning(`Required fields cannot be empty: ${missingNames}!`);
}
// dialogType === 'update'
handleSubmit(formData);
}
function btnPrevDef(e:any) {
e.preventDefault();
viewOnly ? setViewOnly(false) : setViewOnly(true)
}
const onReject = () => {
if (formData.destinationGroup === "Premium" && !formData.approval_description_premium) return toast.warning(`Approval Premium Description needed!`)
if (formData.destinationGroup === "Agent" && !formData.approval_description_premium) return toast.warning(`Approval Agent Description needed!`)
handleReject(formData);
// handleClose();
}
return (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[85%] flex flex-col p-2">
<DialogHeader>
<DialogTitle>Member - {dialogType ==='create' ? "Create" : "View/Edit"}</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody ref={parentRef}>
<div className="flex flex-col">
<form>
{alert.show && (<Alert variant="danger"><h3>{alert.message}</h3></Alert>)}
{/* <div className="card-body grid gap-5"> */}
<div className="card-body grid grid-cols-1 md:grid-cols-2 gap-5">
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, groups, 'group_id', 'Group', true, dialogType === "update"||false): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Username', 'username', 'text', true, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Full Name', 'fullname', 'text', true, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'Photo', 'photouser', previewImg, setPreviewImg): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, genders, 'gender', 'Gender', true, false): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Date of Birth', 'date_birth', 'date', true, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Phone Number', 'msisdn', 'text', true, (dialogType === "create"||formData.group_name === 'Agent')?false:true): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Email', 'email', 'text', true, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Mother Fullname', 'mother_fullname', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, identity_type, 'identity_type', 'Identity Type', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Address', 'address', 'text', true, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Identity Number', 'identity_number', 'text', false, viewOnly): ''}
<div className="bg-white space-y-6">
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, profession, 'profession', 'Profession', false, false): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, municipios, 'municipio_id', 'Municipio', false, false): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, postoAdm, 'posto_adms_id', 'Posto', false, false): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, sucos, 'suco_id', 'Suco', false, false): ''}
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, aldeias, 'aldeia_id', 'Aldeia', false, false): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Nationality', 'nationality', 'text', true, viewOnly): ''}
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"></label>
<ul className="">
{nationality.map((item: any, index) => (
<li key={index} value={item.name}
onClick={() => handleChange({target:{name: 'nationality', value: item.name }})}
className="bg-gray-100 px-4 py-2 rounded hover:bg-gray-200 cursor-default transition">
{item.name}
</li>
))}
{formData.nationality && nationality.length === 0 && (
<li className="text-gray-500">No results found.</li>
)}
</ul>
</div>
<div className="bg-white p-6 rounded-md shadow-md space-y-6">
<h2 className="text-lg font-semibold">Bank Information</h2>
{(formData.id || dialogType === "create") ? generateList(formData, handleChange, banks, 'bank_name', 'Bank Name', false, false): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Bank Account', 'bank_account', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'iBank Number', 'ibank_number', 'text', false, viewOnly): ''}
</div>
</div>
<div className="bg-white p-6 rounded-md shadow-md space-y-6">
<h2 className="text-lg font-semibold">Agent Information</h2>
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'License Number', 'license_number', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Merchant Name', 'agent_name', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Merchant Address', 'merchantaddress', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Longitude Merchant', 'longitudemerchant', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateInput(formData, handleChange, 'Latitude Merchant', 'latitudemerchant', 'text', false, viewOnly): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'Photo Merchant', 'photomerchant', previewImg, setPreviewImg): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Selfie', 'file_selfie', previewImg, setPreviewImg): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document', 'file_document_id', previewImg, setPreviewImg): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Document & Selfie', 'file_document_id_selfie', previewImg, setPreviewImg): ''}
{(formData.id || dialogType === "create") ? generateImage(formData, handleChange, 'File Commercial License', 'file_commercial_license', previewImg, setPreviewImg): ''}
</div>
{(formData.id&&(page==='kyc'&&formData.destinationGroup === "Premium")) ? generateInput(formData, handleChange, 'Approval Premium Description', 'approval_description_premium', 'text', false, viewOnly): ''}
{(formData.id&&(page==='kyc'&&formData.destinationGroup === "Agent")) ? generateInput(formData, handleChange, 'Approval Agent Description', 'approval_description_agent', 'text', false, viewOnly): ''}
{(formData.id && page!=='kyc') ? (
<AdmAccess isAdmin={isAdmin} page={page}groups={groups}formData={formData}fetchCustomers={fetchCustomers}handleClose={handleClose}viewOnly={viewOnly}setViewOnly={setViewOnly}/>
): ""}
{(formData.id && page!=='kyc') ? (
<CustomerWallet customerid={formData.id}/>
) : ""}
<div className="col-span-1 md:col-span-2 flex justify-end gap-5">
<Button type="button" disabled={isAdmin} onClick={(e:any) => btnPrevDef(e)} color="secondary">{ viewOnly ? (`Open Edit`) : (`Close Edit`) }</Button>
<Button type="button" variant="outline" onClick={() => handleAddDialog(false)}>Cancel</Button>
{/* <Button onClick={(e) => buttonOnSubmit(e, formData)} variant="default">Save Changes</Button> */}
{
formData.isneedapproval == 1 && page === 'kyc' ? (
<Button type="button" onClick={onReject} variant="destructive" color="warning">Reject</Button>
) : ('')
}
<Button type="submit" onClick={(e) => buttonOnSubmit(e)} color="primary" variant="default" disabled={isAdmin}>
{ formData.id ? (formData.isneedapproval == 1 && page === 'kyc' ? (`Edit & Approve`) : (`Edit`)) : ("Create") }
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default DetailMember;
{/* <span className="text-red-500 text-[10px] lowercase align-middle ml-1">(agent)</span> */}
function generateInput(formData:any, handleChange:any, label:string, name:string, type: string, required: boolean, disabled: boolean) {
function generateDate(isoString: string) {
return isoString.slice(0, 10); // "2000-01-18"
}
return (
<>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
{label}<span className="text-red-500">{required?"*":""}</span>
</label>
<Input
className="input"
readOnly={disabled}
required={required}
type={type}
name={name}
value={formData[name]?(type === 'date' ? generateDate(formData[name]) : formData[name]):""}
onChange={handleChange}
/>
</div>
</div>
</>
)
}
function generateList(formData:any, handleChange: any, list:any, name:string, label: string, required: boolean, disabled: boolean) {
return (
<>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">{label}
<span className="text-red-600">{required?"*":""}</span></label>
<Select disabled={disabled} required={required} value={formData[name]} onValueChange={(e) => (handleChange({ target : { name, value: e }}))}>
<SelectTrigger>
<SelectValue placeholder={`Select ${label}`} />
</SelectTrigger>
<SelectContent>
{list.map((el: any, idx: any) => (
<SelectItem key={idx} value={el.id}>{el.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</>
)
}
function generateImage(formData:any, handleChange:any, label:string, name:string, previewImg: any, setPreviewImg:any) {
const imagePreview = (file:any) => {
if (file && file.type && file.type.startsWith('image/')) {
const previewURL = URL.createObjectURL(file);
return previewURL
}
return file
};
function handlePrevImg(par: boolean) {
if (par) setPreviewImg({ status: true, img: ''})
if (!par) setPreviewImg({ status: false, img: '' })
}
function onClickImg(img: any) {
setPreviewImg({ status: true, img })
}
return (
<>
{previewImg.status ? (
<PreviewImage previewImgDialog={previewImg.status} handlePreviewImgDialog={handlePrevImg}
modalImage={previewImg.img}/>
):''}
<div className="flex items-center justify-between w-full max-w-xl">
<label className="form-label flex items-center gap-1 max-w-56">
{label}<span className="text-red-500"></span>
</label>
<div className="flex items-center space-x-4 w-3/4 justify-end">
{ formData[name] ? (
<div className="border-2 border-dashed border-red-300 rounded-md p-2">
<img width={300} height={250} srcSet={imagePreview(formData[name])} src={imagePreview(formData[name])}
alt={name} style={{borderRadius: 10, cursor: 'pointer' }} onClick={() => onClickImg(imagePreview(formData[name]))}/>
{/* <img width={300} height={250} srcSet={formData[name]} src={formData[name]} alt={name} style={{borderRadius: 10}}/> */}
</div>
) : (<span>No Data</span>)}
<label className="bg-red-600 text-white px-2 py-1 rounded-full flex items-center cursor-pointer hover:bg-red-800">
<span style={{fontSize: 13}}>Upload</span>
<input type="file" className="hidden" name={name} onChange={handleChange} accept="image/*" />
</label>
</div>
</div>
</>
)
}
function PreviewImage({previewImgDialog, handlePreviewImgDialog, modalImage}:any) {
return (
<>
<Dialog open={previewImgDialog} onOpenChange={(open) => handlePreviewImgDialog(open)}>
<DialogContent className="">
<DialogTitle aria-describedby={''}></DialogTitle>
<DialogBody>
<button
onClick={() => handlePreviewImgDialog(false)}
className="absolute top-2 right-2 text-white bg-red-600 hover:bg-red-800 rounded-full px-3 py-1 text-xl font-bold"
>
&times;
</button>
<img
src={modalImage}
alt="Preview"
className="max-w-full max-h-[80vh] rounded-lg"
/>
</DialogBody>
</DialogContent>
</Dialog>
</>
)
}