Update Member Import

This commit is contained in:
2022-07-25 10:20:35 +07:00
parent 42e4129a79
commit 1edd8157c7
71 changed files with 1917 additions and 2743 deletions

View File

@@ -150,11 +150,13 @@ export default function EditorToolbar({ id, isSimple, ...other }: EditorToolbarP
<select className="ql-align" />
</div>
<div className="ql-formats">
<button type="button" className="ql-link" />
<button type="button" className="ql-image" />
<button type="button" className="ql-video" />
</div>
{!isSimple && (
<div className="ql-formats">
<button type="button" className="ql-link" />
<button type="button" className="ql-image" />
<button type="button" className="ql-video" />
</div>
)}
<div className="ql-formats">
{!isSimple && <button type="button" className="ql-formula" />}

View File

@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
// @mui
import { styled } from '@mui/material/styles';
import { Box, BoxProps } from '@mui/material';

View File

@@ -67,3 +67,49 @@ export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxPr
/>
);
}
// ----------------------------------------------------------------------
interface optionsCustomInterface {
value: string;
label: string;
}
interface RHFCustomMultiCheckboxProps {
name: string;
options: optionsCustomInterface[];
}
export function RHFCustomMultiCheckbox({ name, options, ...other }: RHFCustomMultiCheckboxProps) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field }) => {
const onSelected = (option: optionsCustomInterface) =>
field.value.includes(option.value)
? field.value.filter((value: string) => value !== option.value)
: [...field.value, option.value];
return (
<FormGroup>
{options.map((option, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
checked={field.value.includes(option.value)}
onChange={() => field.onChange(onSelected(option))}
/>
}
label={option.label}
{...other}
/>
))}
</FormGroup>
);
}}
/>
);
}

View File

@@ -0,0 +1,33 @@
// form
import { useFormContext, Controller } from 'react-hook-form';
// @mui
import { TextField, TextFieldProps } from '@mui/material';
import { LocalizationProvider, MobileDatePicker } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
// ----------------------------------------------------------------------
interface IProps {
name: string;
}
export default function RHFDatepicker({ name, ...other }: IProps & TextFieldProps) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
inputFormat="yyyy-MM-dd"
value={field.value}
onChange={field.onChange}
renderInput={(field) => <TextField {...field} fullWidth error={!!error} helperText={error?.message} {...other} />}
/>
</LocalizationProvider>
)}
/>
);
}

View File

@@ -24,6 +24,7 @@ export default function RHFEditor({ name, ...other }: Props) {
value={field.value}
onChange={field.onChange}
error={!!error}
simple={true}
helperText={
<FormHelperText error sx={{ px: 2, textTransform: 'capitalize' }}>
{error?.message}

View File

@@ -8,3 +8,4 @@ export { default as RHFSelect } from './RHFSelect';
export { default as RHFEditor } from './RHFEditor';
export { default as RHFTextField } from './RHFTextField';
export { default as RHFRadioGroup } from './RHFRadioGroup';
export { default as RHFDatepicker } from './RHFDatepicker';

View File

@@ -2,7 +2,7 @@ import * as Yup from 'yup';
import { LoadingButton } from "@mui/lab";
import { Card, Grid, Stack, Typography } from "@mui/material";
import { CorporatePlan } from "../../../@types/corporates";
import { FormProvider, RHFSwitch, RHFTextField } from "../../../components/hook-form";
import { FormProvider, RHFEditor, RHFSwitch, RHFTextField } from "../../../components/hook-form";
import { useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
@@ -30,7 +30,8 @@ export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Prop
() => ({
name: currentCorporatePlan?.name || '',
code: currentCorporatePlan?.code || '',
active: currentCorporatePlan?.active === 1 ? true : false,
active: currentCorporatePlan?.active || true,
description: currentCorporatePlan?.description || ''
}),
[currentCorporatePlan]
);
@@ -110,6 +111,11 @@ export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Prop
<RHFTextField name="code" label="Code" />
<Stack spacing={1}>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Description</Typography>
<RHFEditor name="description" />
</Stack>
<LoadingButton type="submit" variant="contained" size="large" fullWidth={true} loading={isSubmitting}>
Create Corporate Plan
</LoadingButton>

View File

@@ -11,6 +11,7 @@ import useSettings from '../../../hooks/useSettings';
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
// components
import axios from '../../../utils/axios';
import { makeExcerpt } from '../../../utils/formatString';
import { CorporatePlan } from '../../../@types/corporates';
import { LaravelPaginatedData } from '../../../@types/paginated-data';
@@ -74,7 +75,7 @@ export default function PlanList() {
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.code}</TableCell>
<TableCell align="left">{row.name}</TableCell>
<TableCell align="left">{row.description}</TableCell>
<TableCell align="left">{ makeExcerpt(row.description) }</TableCell>
<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>
<TableCell align="right"><Link to={`/corporates/${row.corporate_id}/corporate-plans/${row.id}/edit`}><Button variant="outlined" color="success" size="small">Edit</Button></Link></TableCell>
</TableRow>

View File

@@ -27,10 +27,10 @@ export default function CorporateTabNavigations({ position }: Props) {
'path' : 'plans',
'label': 'Plans',
},
{
'path' : 'corporate-benefits',
'label': 'Corporate Benefit',
},
// {
// 'path' : 'corporate-benefits',
// 'label': 'Corporate Benefit',
// },
{
'path' : 'benefits',
'label': 'Benefit',

View File

@@ -9,6 +9,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { styled } from '@mui/material/styles';
import { LoadingButton } from '@mui/lab';
import {
Box,
Card,
FormHelperText,
Grid,
@@ -23,9 +24,15 @@ import {
RHFRadioGroup,
RHFUploadAvatar,
RHFSwitch,
RHFEditor,
RHFDatepicker,
RHFMultiCheckbox,
RHFCheckbox,
RHFCustomMultiCheckbox,
} from '../../components/hook-form';
import { Corporate } from '../../@types/corporates';
import axios from '../../utils/axios';
import { fCurrency } from '../../utils/formatNumber';
const LabelStyle = styled(Typography)(({ theme }) => ({
...theme.typography.subtitle2,
@@ -65,15 +72,16 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
active: currentCorporate?.id ? currentCorporate?.active === 1 : true,
policy_id: currentCorporate?.current_policy?.id || '',
policy_code: currentCorporate?.current_policy?.code || '',
policy_total_premi: currentCorporate?.current_policy?.total_premi || '',
policy_total_premi: currentCorporate?.current_policy?.total_premi || 0,
policy_minimal_deposit_percentage: currentCorporate?.current_policy?.minimal_deposit_percentage || 50,
policy_minimal_deposit_net: currentCorporate?.current_policy?.minimal_deposit_net || '',
policy_minimal_deposit_net: currentCorporate?.current_policy?.minimal_deposit_net || 0,
policy_minimal_alert_percentage: currentCorporate?.current_policy?.minimal_alert_percentage || 25,
policy_minimal_alert_net: currentCorporate?.current_policy?.minimal_alert_net || '',
policy_minimal_alert_net: currentCorporate?.current_policy?.minimal_alert_net || 0,
policy_stop_service_percentage: currentCorporate?.current_policy?.minimal_stop_service_percentage || 25,
policy_stop_service_net: currentCorporate?.current_policy?.minimal_stop_service_net || '',
policy_stop_service_net: currentCorporate?.current_policy?.minimal_stop_service_net || 0,
policy_start: currentCorporate?.current_policy?.start || '',
policy_end: currentCorporate?.current_policy?.end || '',
linking_rules: currentCorporate?.linking_rules || ['nrik', 'nik', 'member_id'],
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentCorporate]
@@ -104,6 +112,7 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
if (!isEdit) {
reset(defaultValues);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEdit, currentCorporate]);
@@ -148,6 +157,73 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
setValue('logo', null);
};
// Only Handle Change on Policy Total Premi
useEffect(() => {
let calc_policy_minimal_deposit_net = values.policy_total_premi * values.policy_minimal_deposit_percentage / 100;
setValue('policy_minimal_deposit_net', calc_policy_minimal_deposit_net);
let calc_policy_minimal_alert_net = values.policy_total_premi * values.policy_minimal_alert_percentage / 100;
setValue('policy_minimal_alert_net', calc_policy_minimal_alert_net);
let calc_policy_stop_service_net = values.policy_total_premi * values.policy_stop_service_percentage / 100;
setValue('policy_stop_service_net', calc_policy_stop_service_net);
}, [values.policy_total_premi]);
// Only Handle on Change Policy Minimal Deposit
const handleMinimalDepositNetChange = (e) => {
setValue('policy_minimal_deposit_net', e.target.value);
setValue('policy_minimal_deposit_percentage', e.target.value / values.policy_total_premi * 100);
}
const handleMinimalDepositPercentageChange = (e) => {
setValue('policy_minimal_deposit_percentage', e.target.value);
setValue('policy_minimal_deposit_net', values.policy_total_premi * e.target.value / 100);
}
// Only Handle on Change Minimal Alert
const handleMinimalAlertNetChange = (e) => {
setValue('policy_minimal_alert_net', e.target.value);
setValue('policy_minimal_alert_percentage', e.target.value / values.policy_total_premi * 100);
}
const handleMinimalAlertPercentageChange = (e) => {
setValue('policy_minimal_alert_percentage', e.target.value);
setValue('policy_minimal_alert_net', values.policy_total_premi * e.target.value / 100);
}
// Only Handle on Change Minimum Stop Service
const handleStopServiceNetChange = (e) => {
setValue('policy_stop_service_net', e.target.value);
setValue('policy_stop_service_percentage', e.target.value / values.policy_total_premi * 100);
}
const handleStopServicePercentageChange = (e) => {
setValue('policy_stop_service_percentage', e.target.value);
setValue('policy_stop_service_net', values.policy_total_premi * e.target.value / 100);
}
const linking_rules_checkbox_name = "linking_rules"
const linking_tools = [
{
"value" : "nrik",
"label" : "No. KTP"
},
{
"value" : "nik",
"label" : "Nomor Induk Karyawan (NIK)"
},
{
"value" : "member_id",
"label" : "Member ID"
},
{
"value" : "phone",
"label" : "Nomor Telepon"
},
{
"value" : "email",
"label" : "E-Mail"
},
]
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
{/* <Card sx={{ p:3, mb:3, background: 'gray', color: 'white' }}><Typography>Corporate Detail</Typography></Card> */}
@@ -161,9 +237,15 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
<RHFTextField name="name" label="Corporate Name" />
<RHFTextField name="welcome_message" label="Welcome Message" />
<Stack spacing={1}>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Welcome Message</Typography>
<RHFEditor name="welcome_message" />
</Stack>
<RHFTextField name="help_text" label="Help Text" />
<Stack spacing={1}>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Help Text</Typography>
<RHFEditor name="help_text" />
</Stack>
{/* <div>
<LabelStyle>Images</LabelStyle>
@@ -197,6 +279,14 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
/>
</Stack>
</Card>
<Card sx={{ p: 3 }}>
<Stack>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Linking Rules</Typography>
<Stack>
<RHFCustomMultiCheckbox name='linking_rules' options={linking_tools} />
</Stack>
</Stack>
</Card>
</Stack>
</Grid>
@@ -215,49 +305,49 @@ export default function CorporateForm({ isEdit, currentCorporate }: Props) {
</Stack>
{/* <Typography>Minimal Deposit Policy Level</Typography> */}
<Grid container>
<Stack direction="row" spacing={2}>
<Grid item xs={12} md={6}>
<RHFTextField name="policy_start" label="Start Date (YYYY-MM-DD)" />
<RHFDatepicker name="policy_start" label="Start Date (YYYY-MM-DD)" />
</Grid>
<Grid item xs={12} md={6}>
<RHFTextField name="policy_end" label="End Date (YYYY-MM-DD)" />
<RHFDatepicker name="policy_end" label="End Date (YYYY-MM-DD)" />
</Grid>
</Grid>
</Stack>
<RHFTextField name="policy_total_premi" label="Deposit Intial Fund" />
<RHFTextField name="policy_total_premi" label={"Deposit Intial Fund ("+fCurrency(values.policy_total_premi)+")"}/>
<Stack spacing={1}>
<Typography>Minimal Deposit Policy Level</Typography>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Minimal Deposit Policy Level</Typography>
<Grid container>
<Grid item xs={12} md={3}>
<RHFTextField name="policy_minimal_deposit_percentage" label="Percentage (%)" onChange={handleMinimalDepositPercentageChange}/>
</Grid>
<Grid item xs={12} md={9}>
<RHFTextField name="policy_minimal_deposit_net" label={"Net ("+fCurrency(values.policy_minimal_deposit_net)+")"} onChange={handleMinimalDepositNetChange}/>
</Grid>
</Grid>
</Stack>
<Stack spacing={1}>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Minimal Alert Level</Typography>
<Grid container>
<Grid item xs={12} md={3}>
<RHFTextField name="policy_minimal_deposit_percentage" label="Percentage (%)" />
<RHFTextField name="policy_minimal_alert_percentage" label="Percentage (%)" onChange={handleMinimalAlertPercentageChange}/>
</Grid>
<Grid item xs={12} md={9}>
<RHFTextField name="policy_minimal_deposit_net" label="Net (Rp)" />
<RHFTextField name="policy_minimal_alert_net" label={"Net ("+fCurrency(values.policy_minimal_alert_net)+")"} onChange={handleMinimalAlertNetChange}/>
</Grid>
</Grid>
</Stack>
<Stack spacing={1}>
<Typography>Minimal Alert Level</Typography>
<Typography variant='subtitle2' sx={{ color: "text.secondary" }}>Stop Service Level</Typography>
<Grid container>
<Grid item xs={12} md={3}>
<RHFTextField name="policy_minimal_alert_percentage" label="Percentage (%)" />
<RHFTextField name="policy_stop_service_percentage" label="Percentage (%)" onChange={handleStopServicePercentageChange}/>
</Grid>
<Grid item xs={12} md={9}>
<RHFTextField name="policy_minimal_alert_net" label="Net (Rp)" />
</Grid>
</Grid>
</Stack>
<Stack spacing={1}>
<Typography>Stop Service Level</Typography>
<Grid container>
<Grid item xs={12} md={3}>
<RHFTextField name="policy_stop_service_percentage" label="Percentage (%)" />
</Grid>
<Grid item xs={12} md={9}>
<RHFTextField name="policy_stop_service_net" label="Net (Rp)" />
<RHFTextField name="policy_stop_service_net" label={"Net ("+fCurrency(values.policy_stop_service_net)+")"} onChange={handleStopServiceNetChange}/>
</Grid>
</Grid>
</Stack>

View File

@@ -20,6 +20,42 @@ export default function CorporatePlanList() {
const { corporate_id } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState(null)
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = React.useState(true);
const [dataTableData, setDataTableData] = React.useState<LaravelPaginatedData>({
current_page: 1,
data: [],
path: "",
first_page_url: "",
last_page: 1,
last_page_url: "",
next_page_url: "",
prev_page_url: "",
per_page: 10,
from: 0,
to: 0,
total: 0
});
const loadDataTableData = async (appliedFilter = null) => {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/corporates/'+corporate_id+'/members', { params: filter });
// console.log(response.data);
setDataTableLoading(false);
setDataTableData(response.data);
}
const applyFilter = async (searchFilter) => {
await loadDataTableData({ "search" : searchFilter });
setSearchParams({ "search" : searchFilter });
}
useEffect(() => {
loadDataTableData();
}, [])
function SearchInput(props: any) {
// SEARCH
@@ -92,16 +128,13 @@ export default function CorporatePlanList() {
formData.append("file", importPlan.current?.files[0])
axios.post(`corporates/${corporate_id}/members/import`, formData )
.then(response => {
setImportResult(response.data)
handleCancelImportButton();
loadDataTableData();
setImportResult(response.data)
})
.catch(response => {
alert('Looks like something went wrong. Please check your data and try again. ' + response.message)
})
.then(x => {
console.log('motherfucker', importResult)
})
} else {
alert('No File Selected')
}
@@ -206,7 +239,7 @@ export default function CorporatePlanList() {
<TableCell key={ index } align={ column.align } minwidth={ column.minWidth }>{ row[column.id] ?? '-' }</TableCell>
) }
<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>
<TableCell align="right"><Button variant="outlined" color="success" size="small">Edit</Button></TableCell>
{/* <TableCell align="right"><Button variant="outlined" color="success" size="small">Edit</Button></TableCell> */}
</TableRow>
{/* COLLAPSIBLE ROW */}
<TableRow>
@@ -225,46 +258,9 @@ export default function CorporatePlanList() {
);
}
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = React.useState(true);
const [dataTableData, setDataTableData] = React.useState<LaravelPaginatedData>({
current_page: 1,
data: [],
path: "",
first_page_url: "",
last_page: 1,
last_page_url: "",
next_page_url: "",
prev_page_url: "",
per_page: 10,
from: 0,
to: 0,
total: 0
});
const loadDataTableData = async (appliedFilter = null) => {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/corporates/'+corporate_id+'/members', { params: filter });
// console.log(response.data);
setDataTableLoading(false);
setDataTableData(response.data);
}
const headStyle = {
fontWeight: 'bold',
};
const applyFilter = async (searchFilter) => {
await loadDataTableData({ "search" : searchFilter });
setSearchParams({ "search" : searchFilter });
}
useEffect(() => {
loadDataTableData();
}, [])
return (
<Stack>
<ImportForm />

View File

@@ -0,0 +1,11 @@
export function clearTag(htmlString: string | undefined) {
return htmlString?.replace(/(<([^>]+)>)/gi, "");
}
export function limitString(anyString: string | undefined, limit: number = 50) {
return anyString?.substring(0, limit)
}
export function makeExcerpt(htmlString: string | undefined, limit: number = 50) {
return limitString(clearTag(htmlString ?? ''), limit);
}