Files
revenue-fe/src/pages/members/manage-members/CustomerDetailModal.tsx
2025-04-11 15:50:08 +07:00

616 lines
25 KiB
TypeScript

import React, { useState, useEffect } from "react";
import axios from 'axios';
import { Dialog,DialogActions,DialogContent,DialogTitle,TextField,Button,MenuItem,Select,InputLabel,FormControl,Typography,
InputAdornment,Grid,Box,List,ListItem,
} from "@mui/material";
import UploadFileIcon from "@mui/icons-material/UploadFile";
import Divider from '@mui/material/Divider';
import { initialMember } from "./Columns";
import { apiConfig } from '@/config/api.config';
import ConfirmDialog from '@/components/confirm';
import { toast } from 'sonner';
const BASE_URL_MASTER_DATA = apiConfig.service_master_data;
const BASE_URL_CUSTOMER = apiConfig.service_customer;
// MAIN PAGE
const CustomerDialog = ({ open, handleClose, handleSubmit, initialData, viewStats, handleReject, page, fetchCustomers }: any) => {
const [formData, setFormData] = useState(initialData || initialMember);
const [viewOnly, setViewOnly] = useState(viewStats || false);
const [municipios, setMunicipios] = useState([]);
const [aldeias, setAldeias] = useState([]);
const [postoAdm, setPostoAdm] = useState([]);
const [sucos, setSucos] = useState([]);
const [profession, setProfession] = useState([]);
const [groupData] = useState({
reguler: `This fill can not be empty!`,
premium: `This field required only for Premium or Agent`,
agent: `This field required only for Agent`
})
useEffect(() => {
setFormData(initialData || {}); // Sync formData when initialData changes
fetchMasterData()
}, [initialData]);
async function fetchMasterData() {
try {
let getProfession = await axios.get(`${BASE_URL_MASTER_DATA}/profession/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setProfession(getProfession.data.data.list)
let getMunicipios = await axios.get(`${BASE_URL_MASTER_DATA}/municipios/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setMunicipios(getMunicipios.data.data.list)
let getPostoAdms = await axios.get(`${BASE_URL_MASTER_DATA}/postoadms/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setPostoAdm(getPostoAdms.data.data.list)
let getSucos = await axios.get(`${BASE_URL_MASTER_DATA}/sucos/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setSucos(getSucos.data.data.list)
let getAldeias = await axios.get(`${BASE_URL_MASTER_DATA}/aldeias/list`, {
params: {
limit: 50,
page: 1,
with_deleted: false,
order_field: 'name',
order_direction: 'ASC',
}
});
setAldeias(getAldeias.data.data.list)
} catch (error) {
console.log(error);
}
}
const handleChange = async (e: any) => {
const { name, value } = e.target;
if (name === 'municipio' || name === 'posto_adms' || name === 'suco') await getMasterAfter(name, value);
if (name === "file_selfie" || name === "photouser" || name === 'file_document_id' || name === "file_document_id_selfie" ||
name === "file_commercial_license") { // FOR FILE ONLY
setFormData({ ...formData, [name]: e.target.files[0] });
} else {
setFormData({ ...formData, [name]: value });
}
};
async function getMasterAfter(name: string, id: any) {
if (name === 'municipio') {
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') {
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') {
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 onSubmit = () => {
if (page === 'kyc' && !formData.description) return toast.warning(`Description for approval needed!`)
handleSubmit(formData);
// handleClose();
};
const onReject = () => {
handleReject(formData);
handleClose();
}
function generateDate(date: any, type: any) {
if (!date) return ''
const today = new Date(date);
if (type === 'datetime') return today.toISOString().replace('T', ' ').substring(0, 19);
return today.toISOString().split('T')[0];
}
return (
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
<DialogTitle>Customer Form ({viewOnly ? 'View' : 'Edit'})</DialogTitle>
<DialogContent>
<div className="flex justify-between pb-5">
<Typography sx={{color:'grey'}}>Customer Data</Typography>
<Typography sx={{color:'grey'}} display="flex" justifyContent="flex-end">Created: {generateDate(formData.created_at, 'datetime')}</Typography>
</div>
{
formData.isneedapproval == 1 ? (
<div className="flex justify-between pb-5">
<Typography sx={{color:'orange'}}>User Request for approval </Typography>
</div>
) : ('')
}
<TextField disabled={viewOnly} required multiline fullWidth margin="dense" label="Full Name" name="fullname" value={formData.fullname} onChange={handleChange} />
{/* <Typography fontSize={13} paddingLeft={2} marginTop={-2.5} color="red">{groupData.reguler}</Typography> */}
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Email" name="email" value={formData.email} onChange={handleChange} />
<TextField disabled fullWidth required margin="dense" label="Group" name="group" value={formData.group_name} onChange={handleChange} />
{fileTextFile("Photo", formData.photouser, "photouser", handleChange)}
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Username" name="username" value={formData.username} onChange={handleChange} />
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Mother Fullname" name="mother_fullname" value={formData.mother_fullname} onChange={handleChange} />
<TextField disabled={viewOnly} required fullWidth margin="dense" label="MSISDN" name="msisdn" value={formData.msisdn} onChange={handleChange} />
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Address" name="address" value={formData.address} onChange={handleChange} />
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Nationality" name="nationality" value={formData.nationality} onChange={handleChange} />
<TextField disabled={viewOnly} required fullWidth margin="dense" label="Date of Birth" name="date_birth" type="date" value={generateDate(formData.date_birth, null)} onChange={handleChange} InputLabelProps={{ shrink: true }} />
<FormControl required fullWidth margin="dense">
<InputLabel>Gender</InputLabel>
<Select required disabled={viewOnly} name="gender" value={formData.gender} onChange={handleChange}>
<MenuItem key={1} value="M">Male</MenuItem>
<MenuItem key={2} value="F">Female</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel>Identity Type</InputLabel>
<Select disabled={viewOnly} name="identity_type" value={formData.identity_type} onChange={handleChange}>
<MenuItem key={1} value="eleitoral_id">Eleitoral ID</MenuItem>
<MenuItem key={2} value="bihete_de_identidade">Bihete de Identidade</MenuItem>
<MenuItem key={3} value="passport">Passport</MenuItem>
</Select>
</FormControl>
{/* AGENT & PREMIUM DATA */}
<TextField disabled={viewOnly} fullWidth margin="dense" label="Identity Number" name="identity_number" value={formData.identity_number} onChange={handleChange} />
<TextField disabled={viewOnly} type="text" fullWidth margin="dense" label="License Number" name="license_number" value={formData.license_number} onChange={handleChange} />
<TextField disabled={viewOnly} type="text" fullWidth margin="dense" label="Merchant Address" name="merchantaddress" value={formData.merchantaddress} onChange={handleChange} />
<TextField disabled={viewOnly} type="text" fullWidth margin="dense" label="Longitude Merchant" name="longitudemerchant" value={formData.longitudemerchant} onChange={handleChange} />
<TextField disabled={viewOnly} type="text" fullWidth margin="dense" label="Latitude Merchant" name="latitudemerchant" value={formData.latitudemerchant} onChange={handleChange} />
{fileTextFile("File Selfie", formData.file_selfie, "file_selfie", handleChange)}
<img width={300} height={250} srcSet={formData.file_selfie} src={formData.file_selfie} alt={"file_selfie"} style={{borderRadius: 10}}/>
{fileTextFile("File Document", formData.file_document_id, "file_document_id", handleChange)}
<img width={300} height={250} srcSet={formData.file_document_id} src={formData.file_document_id} alt={"file_document_id"} style={{borderRadius: 10}}/>
{fileTextFile("File Document & Selfie", formData.file_document_id_selfie, "file_document_id_selfie", handleChange)}
<img width={300} height={250} srcSet={formData.file_document_id_selfie} src={formData.file_document_id_selfie} alt={"file_document_id_selfie"} style={{borderRadius: 10}}/>
{fileTextFile("File Commercial License", formData.file_commercial_license, "file_commercial_license", handleChange)}
<img width={300} height={250} srcSet={formData.file_commercial_license} src={formData.file_commercial_license} alt={"file_commercial_license"} style={{borderRadius: 10}}/>
{/* AGENT & PREMIUM DATA */}
{/* <TextField disabled={viewOnly} fullWidth margin="dense" label="Profession" name="profession" value={formData.profession} onChange={handleChange} /> */}
<FormControl fullWidth margin="dense">
<InputLabel>Profession</InputLabel>
<Select disabled={viewOnly} name="profession" value={formData.profession} onChange={handleChange}>
{
profession ? profession.map((el: any) => (
<MenuItem key={el.id} value={el.id}>{el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
{/* <TextField fullWidth margin="dense" label="Password" name="password" type="password" value={formData.password} onChange={handleChange} /> */}
{/* <TextField fullWidth margin="dense" label="PIN" name="pin" value={formData.pin} onChange={handleChange} /> */}
<FormControl fullWidth margin="dense">
<InputLabel>Status</InputLabel>
<Select disabled={viewOnly} name="status" value={formData.status} onChange={handleChange}>
<MenuItem key={1} value="Y">Active</MenuItem>
<MenuItem key={2} value="N">Inactive</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel>Municipio</InputLabel>
<Select disabled={viewOnly} name="municipio" value={formData.municipio} onChange={handleChange}>
{
municipios ? municipios.map((el: any) => (
<MenuItem key={el.id} value={el.id}>{el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel>Posto</InputLabel>
<Select disabled={viewOnly || !formData.municipio} name="posto_adms" value={formData.posto_adms} onChange={handleChange}>
{
postoAdm ? postoAdm.map((el: any) => (
<MenuItem key={el.posto_adms_id || el.id} value={el.posto_adms_id || el.id}>{el.posto_adms_name || el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel>Suco</InputLabel>
<Select disabled={viewOnly || !formData.posto_adms} name="suco" value={formData.suco} onChange={handleChange}>
{
sucos ? sucos.map((el: any) => (
<MenuItem key={el.sucos_id || el.id} value={el.sucos_id || el.id}>{el.sucos_name || el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
<FormControl fullWidth margin="dense">
<InputLabel>Aldeia</InputLabel>
<Select disabled={viewOnly || !formData.suco} name="aldeia" value={formData.aldeia} onChange={handleChange}>
{
aldeias ? aldeias.map((el: any) => (
<MenuItem key={el.id} value={el.id}>{el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
<Divider className="pt-7"/>
<Typography sx={{color:'grey'}}>Bank</Typography>
<FormControl fullWidth margin="dense">
<InputLabel>Bank Name</InputLabel>
<Select disabled={viewOnly} name="bank_name" value={formData.bank_name} onChange={handleChange}>
<MenuItem key={1} value="BNCTL">BNCTL</MenuItem>
<MenuItem key={2} value="BRI">BRI</MenuItem>
<MenuItem key={3} value="BNU">BNU</MenuItem>
<MenuItem key={4} value="Mandiri">Mandiri</MenuItem>
</Select>
</FormControl>
<TextField disabled={viewOnly} fullWidth margin="dense" label="Bank Account" name="bank_account" value={formData.bank_account} onChange={handleChange} />
<TextField disabled={viewOnly} fullWidth margin="dense" label="iBank Number" name="ibank_number" value={formData.ibank_number} onChange={handleChange} />
<Divider className="pt-7"/>
{/* {getAdmAccess(page, formData, handleClose, fetchCustomers)} */}
{ page === 'kyc' ? (
<>
<Typography sx={{color:'grey'}}>Approval</Typography>
<TextField fullWidth required={page === 'kyc'?true:false} margin="dense" label="Approval Description" name="description" value={formData.description} onChange={handleChange} />
</>
) : (<>
{getAdmAccess(page, formData, handleClose, fetchCustomers, viewOnly, setViewOnly)}
<Divider className="pt-7"/>
{formData.id ? showCustomerWallet(formData.id) : ""}
</>)
}
</DialogContent>
<DialogActions>
{
page === 'kyc' ? (
<Button onClick={() => viewOnly ? setViewOnly(false) : setViewOnly(true)} color="secondary">{ viewOnly ? (`Open Edit`) : (`Close Edit`) }</Button>
) : ('')
}
<Button onClick={handleClose} color="secondary">Cancel</Button>
{
formData.isneedapproval == 1 && page === 'kyc' ? (
<Button onClick={onReject} color="warning" variant="contained">Reject</Button>
) : ('')
}
<Button onClick={onSubmit} color="primary" variant="contained">
{ formData.id ? (formData.isneedapproval == 1 && page === 'kyc' ? (`Edit & Approve`) : (`Edit`)) : ("Create") }
</Button>
</DialogActions>
</Dialog>
);
};
export default CustomerDialog;
function fileTextFile(label: string, value: any, name: string, handleChange: any) {
return (
<TextField
margin="dense"
label={label}
variant="outlined"
fullWidth
value={value}
placeholder="Choose a file..."
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<input
type="file"
id="file-upload"
// style={{ display: "none" }}
onChange={handleChange}
name={name}
/>
{/* <label htmlFor="file-upload">
<Button
component="span"
variant="contained"
color="primary"
startIcon={<UploadFileIcon />}
>
Browse
</Button>
</label> */}
</InputAdornment>
),
}}
/>
)
}
// ACCESS ADM
function getAdmAccess(page: string, data: any, handleClose: any, fetchCustomers: any, viewOnly: any, setViewOnly: any) {
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogType, setDialogType] = useState('');
const [changeGroup, setChangeGroup] = useState('');
const [changeGroupD, setChangeGroupD] = useState(false);
const [groups, setGroups] = useState([]);
useEffect(() => {
fetchGroups()
}, []);
const fetchGroups = async () =>{
try {
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);
} catch (error: any) {
console.error(error.message);
toast.error(error.message)
}
}
const handleYes = async () => {
try {
if (dialogType === "update status") {
let statusNext = getPinStatus(data.status).res;
if (statusNext) await axios.put(`${BASE_URL_CUSTOMER}/customer/statuspin`, { customerid: data.id, status: statusNext })
else toast.error("Handle Active/Suspend only")
toast.success("Success Update Status")
}
if (dialogType === "reset pin") {
if (data.id) await axios.post(`${BASE_URL_CUSTOMER}/customer/resetpin`, { customerid: data.id })
else throw({ message: 'data.id not found' })
toast.success("Pin will send to customer MSISDN")
}
} catch (error: any) {
toast.error(error.message)
} finally {
setDialogOpen(false)
handleClose()
}
}
function buttonStatus() {
setDialogType('update status')
setDialogOpen(true)
}
function buttonResetPin() {
setDialogType('reset pin')
setDialogOpen(true)
}
async function buttonChangeGroup() {
try {
let dataObj = {
customerid: data.id,
destination_group: changeGroup
}
if (data.group_id === changeGroup) return toast.warning(`You update same group as the exist customer group`)
if (dataObj.customerid && dataObj.destination_group) {
await axios.post(`${BASE_URL_CUSTOMER}/customer/change-group`, dataObj)
}
await fetchCustomers()
toast.success('Success Change group')
} catch (error: any) {
console.log(error);
toast.error(error.message)
} finally {
await fetchCustomers()
setChangeGroupD(false)
handleClose()
}
}
function openChangeGroupDialog() {
setChangeGroup(data.group_id);
setChangeGroupD(true)
}
if (page !== "kyc") {
return (
<Box p={3} boxShadow={3} borderRadius={2} bgcolor="white">
<Typography variant="h6" gutterBottom>Access Administration</Typography>
<Grid container spacing={3}>
<Grid item xs={6} container direction="column" spacing={2}>
<Grid item>
<Typography variant="body2">Pin Status : {getPinStatus(data.status).msg}</Typography>
<Button onClick={() => buttonStatus()} variant="contained" color="primary">{getPinStatus(data.status).btn}</Button>
</Grid>
<Grid item>
<Typography variant="body2">Reset PIN</Typography>
<Button onClick={() => buttonResetPin()} variant="contained" color="primary">Reset PIN</Button>
</Grid>
</Grid>
<Grid item xs={6} container direction="column" spacing={2}>
<Grid item>
<Typography variant="body2">Change Group</Typography>
<Button onClick={() => openChangeGroupDialog()} variant="contained" color="primary">Change Group</Button>
</Grid>
<Grid item>
<Typography variant="body2">Edit Member</Typography>
{/* <Button variant="contained" color="primary">Edit Member</Button> */}
<Button onClick={() => viewOnly ? setViewOnly(false) : setViewOnly(true)} variant="contained">{ viewOnly ? (`Open Edit`) : (`Close Edit`) }</Button>
</Grid>
</Grid>
</Grid>
<Dialog open={changeGroupD} onClose={() => setChangeGroupD(false)} fullWidth>
<Grid container padding={3}>
<Typography variant="h6" gutterBottom color="orange">Are you sure to change customer Group?</Typography>
<FormControl fullWidth margin="dense">
<Typography gutterBottom>Destination Group</Typography>
<Select name="groups" value={changeGroup} onChange={(e: any) => setChangeGroup(e.target.value)}>
{
groups ? groups.map((el: any) => (
<MenuItem key={el.id} value={el.id}>{el.name}</MenuItem>
)) : ""
}
</Select>
</FormControl>
</Grid>
<DialogActions>
<Button onClick={() => setChangeGroupD(false)} color="secondary">
No
</Button>
<Button onClick={buttonChangeGroup} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
<ConfirmDialog
open={dialogOpen}
onClose={() => setDialogOpen(false)}
title="Confirm Action"
content={`Are you sure you want to ${dialogType}?`}
onYes={handleYes}
onNo={() => setDialogOpen(false)}
/>
</Box>
)
} else {
return ('')
}
}
function getPinStatus(status: string) {
if (status === "Y") return {
msg: 'Active',
btn: 'Block PIN',
res: 'Block'
};
if (status === "N") return {
msg: 'Not Active',
btn: 'Activate PIN',
res: null
};
if (status === "P") return {
msg: 'Suspend PIN',
btn: 'Unblock PIN',
res: 'UnBlock'
};
if (status === "O") return {
msg: 'Suspend OTP',
btn: 'Unblock OTP',
res: null
};
return {
msg: "None",
btn: 'No status found',
res: null
}
}
// CUSTOMER WALLET
function showCustomerWallet(customerid: any) {
const [customerWallet, setCustomerWallet] = useState([]);
if (!customerid) return ''
useEffect(() => {
fetchCustomerWallet()
}, []);
async function fetchCustomerWallet() {
try {
let getCustWallet = await axios.get(`${BASE_URL_CUSTOMER}/customer/wallet`, { params: { customerid: customerid }});
setCustomerWallet(getCustWallet.data.data.wallets)
} catch (error: any) {
toast.error(error.message)
}
}
return (
<Box p={3} boxShadow={3} borderRadius={2} bgcolor="white">
<Typography variant="h6" gutterBottom>Wallet Member</Typography>
<Grid>
<List>
{customerWallet.length ? (customerWallet.map((item:any, index:any) => (
<React.Fragment key={item.id}>
<ListItem
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
px: 2,
py: 1.5,
bgcolor: index % 2 === 0 ? 'grey.50' : 'background.paper',
borderRadius: 2,
'&:hover': {
bgcolor: 'grey.100',
},
}}
>
<Box>
<Typography variant="subtitle2" color="text.secondary">Name</Typography>
<Typography variant="body1" fontWeight={500}>
{item.wallet.name}
</Typography>
</Box>
<Box>
<Typography variant="subtitle2" color="text.secondary">Description</Typography>
<Typography variant="body1">{item.wallet.description}</Typography>
</Box>
<Box>
<Typography variant="subtitle2" color="text.secondary">Balance</Typography>
<Typography variant="body1">{item.amount}</Typography>
</Box>
<Box>
<Typography variant="subtitle2" color="text.secondary">Transaction Today</Typography>
<Typography variant="body1">{item.transaction_number_today}</Typography>
</Box>
</ListItem>
{index < customerWallet.length - 1 && <Divider sx={{ my: 1 }} />}
</React.Fragment>
))): "No wallet"}
</List>
</Grid>
</Box>
)
}