Commit 23684781 authored by ardiansyah's avatar ardiansyah

Merge branch 'ENV-DEV' into 'ENV-STAGING'

Env dev

See merge request !2383
parents c380b498 23f63534
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tia-dev.iml" filepath="$PROJECT_DIR$/.idea/tia-dev.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
......@@ -29,6 +29,24 @@ const create = (type = "") => {
timeout: 300000
})
break;
case 'DOWNLOAD':
api = apisauce.create({
// base URL is read from the "constructor"
baseURL,
// here are some default headers
headers: {
'Cache-Control': 'no-cache',
Accept: 'application/json',
'Content-Type': 'application/json',
},
responseType: 'blob',
// 3 mins timeout...
// timeout: 180000
// 5 mins timeout...
timeout: 300000
})
break;
case 'UPLOAD':
api = apisauce.create({
// base URL is read from the "constructor"
......@@ -561,6 +579,9 @@ const create = (type = "") => {
const validateSaveLOCF = (body) => api.post('transaction/locf/monthly_report/validate_save', body)
const createMonthlyReportLOV = (body) => api.post('transaction/lov/monthly_report/save_monthly_report', body)
// Historical
const getHistoricalReport = () => api.get('transaction/historical/get/report')
const exportReportHistorial = (body) => api.get(`transaction/historical/export_report?report_id=${body.report_id}&company_id=${body.company_id}&year=${body.year}&month=${body.month}&performance_period=${body.performance_period}`)
// Superadmin Approve
const getListApprover = (report, monthlyReportId) => api.get(`transaction/${report}/get_approver/${monthlyReportId}`)
......@@ -972,8 +993,9 @@ const create = (type = "") => {
getTypeOfInvestment,
validateSaveLOCF,
validateSaveLOV,
createMonthlyReportLOV
createMonthlyReportLOV,
getHistoricalReport,
exportReportHistorial
}
}
......
import React, { Component } from 'react'
import { Paper, } from '@material-ui/core';
import AlertSnackbar from '../../library/AlertSnackbar'
import SectionHeader from '../../library/SectionHeader';
import AutocompleteField from '../../library/AutocompleteField';
import CustomButton from '../../library/CustomButton';
import api from '../../api';
import DDLYear from '../../library/Dropdown/DDLYear';
import DDLMonth from '../../library/Dropdown/DDLMonth';
import DDLCompany from '../../library/Dropdown/DDLCompany';
import ContentContainer from '../../library/ContentContainer';
import Constant from '../../library/Constant';
import { downloadFileBlob } from '../../library/Utils';
class ReportHistorical extends Component {
constructor(props) {
super(props)
this.state = {
showAlert: false,
alertMessage: '',
alertSeverity: Constant.ALERT_SEVIRITY.SUCCESS,
selectedValue: null,
data: {},
isLoading: false,
listReportType: [],
listPeriodType: [
{ id: 'MTD', name: 'MTD', },
{ id: 'YTD', name: 'YTD', }
],
buttonError: false,
}
}
componentDidMount() {
this.getData()
}
setLoading = (isLoading) => {
this.setState({ isLoading })
}
getData() {
this.setLoading(true)
api.create().getHistoricalReport().then((res) => {
const list = res.data?.data || []
const arr = []
list.forEach(item => {
arr.push({ id: item.report_type_id, name: item.report_name })
})
this.setState({ listReportType: arr }, () => {
this.setDefaultData()
})
})
}
setDefaultData = () => {
const defaultReportType = this.state.listReportType[0] || null
const defaultPeriodType = this.state.listPeriodType[0] || null
this.setState({
data: {
...this.state.data,
report_id: defaultReportType,
performance_period: defaultPeriodType,
}
}, () => {
this.setLoading(false)
})
}
handleChangeDropdown = (newValue, name) => {
this.setState(prevState => ({
data: {
...prevState.data,
[name]: newValue,
}
}))
}
handleChangeMultiDropdown = (newValues, name) => {
this.setState(prevState => ({
data: {
...prevState.data,
[name]: newValues,
}
}))
}
showAlert = (message, severity = 'success') => {
this.setState({
showAlert: true,
alertMessage: message,
alertSeverity: severity,
});
};
closeAlert = () => {
this.setState({ showAlert: false });
};
handleDownload = async () => {
try {
this.setLoading(true)
const payload = {
report_id: this.state.data.report_id?.id,
company_id: this.state.data.company_id?.map(c => c.id).join(','),
performance_period: this.state.data.performance_period?.id,
year: this.state.data.year?.id,
month: this.state.data.month?.id,
}
api.create('DOWNLOAD').exportReportHistorial(payload).then(async (res) => {
this.setLoading(false)
const blob = res.data
if (blob && blob.size > 0) {
downloadFileBlob('Historical.xlsx', blob)
}
this.showAlert('Download Berhasil', 'success');
})
} catch (error) {
// Show error alert
this.showAlert(`Gagal menyimpan: ${error.message}`, 'error');
}
};
render() {
const { data, showAlert, alertMessage, alertSeverity, listReportType, listPeriodType, isLoading } = this.state;
const contentStyle = { display: 'flex', marginTop: 10, gap: '20px' };
return (
<ContentContainer isLoading={isLoading} title="Report Historical">
<div style={{ padding: 20 }}>
<Paper style={{ paddingTop: 10, paddingBottom: 20 }}>
<SectionHeader
title="Report Historical"
/>
<div style={{ padding: '20px 20px 0px 20px' }}>
<div style={contentStyle}>
<AutocompleteField
options={listReportType}
value={data?.report_id}
onChange={(event, newValue) => this.handleChangeDropdown(newValue, 'report_id')}
label="Report Type"
isLoading={isLoading}
/>
<AutocompleteField
options={listPeriodType}
value={data?.performance_period}
onChange={(event, newValue) => this.handleChangeDropdown(newValue, 'performance_period')}
label="Period Type"
/>
</div>
<div style={contentStyle}>
<DDLMonth
value={data?.month}
name="month"
useCurrentMonthAsDefault={true}
onChange={(event, newValue, name) => this.handleChangeDropdown(newValue, name)}
/>
<DDLYear
value={data?.year}
name="year"
useCurrentYearAsDefault={true}
onChange={(event, newValue, name) => this.handleChangeDropdown(newValue, name)}
/>
</div>
<DDLCompany
multiple
value={data?.company_id}
name={"company_id"}
onChange={(event, newValue, name) => this.handleChangeDropdown(newValue, name)}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', paddingLeft: 20, paddingRight: 20, marginTop: 20 }}>
<CustomButton
disabled={this.state.buttonError}
onClick={this.handleDownload}
>
Download
</CustomButton>
</div>
</Paper>
</div>
<AlertSnackbar
open={showAlert}
message={alertMessage}
severity={alertSeverity}
onClose={this.closeAlert}
/>
</ContentContainer>
)
}
}
export default ReportHistorical
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import MuiAlert from '@material-ui/lab/Alert';
const Alert = withStyles({})((props) => (
<MuiAlert elevation={6} variant="filled" {...props} />
));
export default Alert;
\ No newline at end of file
import React, { Component } from 'react';
import { Snackbar } from '@material-ui/core';
import Alert from './Alert';
import Constant from './Constant';
class AlertSnackbar extends Component {
state = {
open: false,
message: '',
severity: Constant.ALERT_SEVIRITY.SUCCESS,
};
componentDidMount() {
const { open, message, severity } = this.props;
if (open !== undefined) {
this.setState({ open });
}
if (message) {
this.setState({ message });
}
if (severity) {
this.setState({ severity });
}
}
componentDidUpdate(prevProps) {
const { open, message, severity } = this.props;
if (prevProps.open !== open && open !== undefined) {
this.setState({ open });
}
if (prevProps.message !== message && message) {
this.setState({ message, open: true });
}
if (prevProps.severity !== severity) {
this.setState({ severity });
}
}
handleClose = (event, reason) => {
const { onClose, disableClickawayClose = false } = this.props;
if (disableClickawayClose && reason === 'clickaway') {
return;
}
this.setState({ open: false });
if (onClose) {
onClose(event, reason);
}
};
render() {
const {
// Props untuk custom content
children,
// Props untuk Snackbar
autoHideDuration = 2000,
anchorOrigin = { vertical: 'bottom', horizontal: 'center' },
// Props untuk Alert
action,
icon,
onClose: propOnClose,
// Lainnya
...snackbarProps
} = this.props;
const { open, message, severity } = this.state;
return (
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={this.handleClose}
anchorOrigin={anchorOrigin}
{...snackbarProps}
>
<Alert
onClose={this.handleClose}
severity={severity}
action={action}
icon={icon}
>
{children || message}
</Alert>
</Snackbar>
);
}
}
export default AlertSnackbar;
\ No newline at end of file
import React from 'react';
import { TextField, Checkbox } from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '@material-ui/icons/CheckBox';
import CircularProgress from '@material-ui/core/CircularProgress';
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const AutocompleteField = ({
options = [],
value,
onChange,
label,
id = "autocomplete-field",
name,
style = { width: 250 },
margin = 'normal',
disabled = false,
required = false,
error = false,
helperText,
disableClearable = true,
renderInput,
noOptionsText = "No options available",
multiple = false,
showCheckbox = false,
isLoading = false,
...props
}) => {
const defaultRenderInput = (params) => (
<TextField
{...params}
label={label}
margin={margin}
style={{ marginTop: 7 }}
disabled={disabled}
required={required}
error={error}
helperText={helperText}
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{isLoading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
);
// Custom renderOption untuk checkbox
const renderOptionWithCheckbox = (option, state) => {
// Manually check if option is selected
const isSelected = multiple && Array.isArray(value)
? value.some(v => v.id === option.id)
: false;
return (
<div
style={{
display: 'flex',
alignItems: 'center',
width: '100%',
padding: '6px 10px',
cursor: 'pointer',
}}
>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={isSelected}
style={{ marginRight: 8 }}
color="primary"
/>
{option.name}
</div>
);
};
return (
<Autocomplete
multiple={multiple}
options={options}
onChange={onChange}
value={multiple ? (value || []) : (value || null)}
id={id}
disableClearable={!multiple && disableClearable}
disableCloseOnSelect={multiple}
style={style}
disabled={disabled}
renderInput={renderInput || defaultRenderInput}
getOptionLabel={(option) => option?.name || ""}
noOptionsText={noOptionsText}
isOptionEqualToValue={(option, value) => {
if (!option || !value) return false;
return option.id === value.id;
}}
renderOption={showCheckbox && multiple ? renderOptionWithCheckbox : undefined}
loadingText="Loading..."
{...props}
/>
);
};
export default AutocompleteField;
\ No newline at end of file
......@@ -12,9 +12,15 @@ const Constant = {
URL_FE_STAGING: 'https://tia.eksad.com/tia-web-staging',
URL_FE_DEMO: 'https://tia.eksad.com/tia-web-demo',
URL_FE_PROD: 'https://dashboard.triputra-group.com/tia-web',
DATACAT: 'datacat'
DATACAT: 'datacat',
// URL_BE_MAIN : Constant.URL_BE_DEV,
// URL_FE_MAIN : Constant.URL_FE_DEV,
ALERT_SEVIRITY : {
SUCCESS: 'success',
ERROR: 'error',
WARNING: 'warning',
},
}
......
import React, { Component } from 'react';
import OverlayLoader from './OverlayLoader';
import Header from './Header';
class ContentContainer extends Component {
render() {
const {
children,
isLoading = false,
backgroundColor = '#f8f8f8',
flex = 1,
padding = 0,
style = {},
loaderProps = {},
title = '',
...restProps
} = this.props;
const containerStyle = {
flex: flex,
backgroundColor: backgroundColor,
padding: padding,
minHeight: '100px', // minimal height agar loader visible
...style,
};
return (
<div style={containerStyle} {...restProps}>
{isLoading && (
<OverlayLoader isLoading={isLoading} {...loaderProps} />
)}
<Header
title={title}
/>
{children}
</div>
);
}
}
export default ContentContainer;
\ No newline at end of file
import React from 'react';
import { Typography } from '@material-ui/core';
const CustomButton = ({
children,
onClick,
disabled = false,
type = 'button',
width = 100,
height = 25,
backgroundColor = '#354960',
textColor = '#fff',
fontSize = '11px',
borderRadius = 3,
// Styling props
buttonStyle = {},
textStyle = {},
wrapperStyle = {},
// Icon props
startIcon,
endIcon,
// Variants
variant = 'contained', // 'contained' | 'outlined' | 'text'
...props
}) => {
// Variant styling
const getVariantStyles = () => {
switch (variant) {
case 'outlined':
return {
backgroundColor: 'transparent',
border: `1px solid ${backgroundColor}`,
};
case 'text':
return {
backgroundColor: 'transparent',
borderColor: 'transparent',
};
default: // contained
return {
backgroundColor,
borderColor: backgroundColor,
};
}
};
const getTextColor = () => {
if (variant === 'outlined' || variant === 'text') {
return backgroundColor;
}
return textColor;
};
return (
<button
type={type}
disabled={disabled}
onClick={onClick}
style={{
backgroundColor: 'transparent',
borderColor: 'transparent',
outline: 'none',
cursor: disabled ? 'not-allowed' : 'pointer',
padding: 0,
...buttonStyle,
}}
{...props}
>
<div
style={{
backgroundColor: getVariantStyles().backgroundColor,
border: getVariantStyles().border || 'none',
width,
height,
borderRadius,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
opacity: disabled ? 0.5 : 1,
...wrapperStyle,
}}
>
{startIcon && (
<span style={{ marginRight: 4, display: 'flex', alignItems: 'center' }}>
{startIcon}
</span>
)}
<Typography
style={{
fontSize,
color: getTextColor(),
textAlign: 'center',
...textStyle,
}}
>
{children}
</Typography>
{endIcon && (
<span style={{ marginLeft: 4, display: 'flex', alignItems: 'center' }}>
{endIcon}
</span>
)}
</div>
</button>
);
};
export default CustomButton;
\ No newline at end of file
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AutocompleteField from '../AutocompleteField';
import api from '../../api';
class DDLCompany extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: props.multiple
? (props.value || [])
: (props.value || null),
companies: [],
isLoading: false,
};
}
componentDidMount() {
this.getCompanyActive();
}
componentDidUpdate(prevProps, prevState) {
// Value dikontrol parent
if (prevProps.value !== this.props.value) {
this.syncValueWithCompanies(this.props.value);
}
// Companies berubah (hasil API)
if (prevState.companies !== this.state.companies) {
this.syncValueWithCompanies(this.props.value);
}
}
setLoading = (isLoading) => {
this.setState({ isLoading });
}
getCompanyActive = async () => {
try {
this.setLoading(true);
const response = await api.create().getPerusahaanActive();
const data = response?.data?.data || [];
const companies = data.map(item => ({
id: String(item.company_id),
name: item.company_name,
}));
this.setState({ companies });
} catch (err) {
console.error('Failed to load companies', err);
this.setState({ companies: [] });
} finally {
this.setLoading(false);
}
};
// Sinkron value → referensi object dari companies
syncValueWithCompanies = (value) => {
const { companies } = this.state;
const { multiple } = this.props;
if (!value || companies.length === 0) {
this.setState({
selectedValue: multiple ? [] : null,
});
return;
}
if (multiple) {
const synced = value
.map(v =>
companies.find(c => String(c.id) === String(v.id))
)
.filter(Boolean);
this.setState({ selectedValue: synced });
} else {
const matched = companies.find(
c => String(c.id) === String(value.id)
);
this.setState({ selectedValue: matched || null });
}
};
handleChange = (event, newValue) => {
const { onChange, onCompanyChange, name, multiple } = this.props;
this.setState({ selectedValue: newValue });
// Standard handler
if (onChange) {
onChange(event, newValue, name);
}
// Backward compatibility
if (onCompanyChange) {
if (multiple) {
onCompanyChange(newValue.map(v => v.id));
} else {
onCompanyChange(newValue ? newValue.id : null);
}
}
};
getSelectedCompanyValue = () => {
const { selectedValue } = this.state;
const { multiple } = this.props;
return multiple
? selectedValue.map(v => v.id)
: selectedValue?.id || null;
};
render() {
const {
label = 'Company',
placeholder = 'Select Company',
disabled = false,
required = false,
error = false,
helperText,
style = { width: 250 },
margin = 'normal',
multiple,
} = this.props;
const { selectedValue, companies, isLoading } = this.state;
return (
<AutocompleteField
options={companies}
value={selectedValue}
onChange={this.handleChange}
label={label}
placeholder={placeholder}
disabled={disabled}
required={required}
error={error}
helperText={helperText}
style={style}
margin={margin}
multiple={multiple}
showCheckbox={multiple}
isLoading={isLoading}
/>
);
}
}
DDLCompany.propTypes = {
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.array,
]),
onChange: PropTypes.func,
onCompanyChange: PropTypes.func,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
style: PropTypes.object,
margin: PropTypes.string,
disabled: PropTypes.bool,
required: PropTypes.bool,
error: PropTypes.bool,
helperText: PropTypes.string,
multiple: PropTypes.bool,
};
DDLCompany.defaultProps = {
label: 'Company',
placeholder: 'Select Company',
style: { width: 250 },
multiple: false,
};
export default DDLCompany;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AutocompleteField from '../AutocompleteField';
class DDLMonth extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: props.value || null,
months: [],
};
}
componentDidMount() {
this.generateMonthOptions();
// Set default value if enabled and no value provided
if (this.props.useCurrentMonthAsDefault && !this.props.value) {
setTimeout(() => this.setDefaultMonth(), 0);
} else if (this.props.value) {
setTimeout(() => this.syncValueWithMonths(this.props.value), 0);
}
}
componentDidUpdate(prevProps, prevState) {
// Update if value prop changes
if (prevProps.value !== this.props.value) {
this.syncValueWithMonths(this.props.value);
}
// Update if month format or locale changes
if (prevProps.monthFormat !== this.props.monthFormat ||
prevProps.locale !== this.props.locale ||
prevProps.disabledMonths !== this.props.disabledMonths) {
this.generateMonthOptions();
}
// Sync value after months regenerated
if (prevState.months !== this.state.months) {
if (this.props.value) {
this.syncValueWithMonths(this.props.value);
} else if (this.props.useCurrentMonthAsDefault) {
this.setDefaultMonth();
}
}
}
// Set default month (current month)
setDefaultMonth = () => {
const { months } = this.state;
const { onChange, name } = this.props;
if (months.length === 0) return;
const currentMonth = new Date().getMonth() + 1; // 1-12
const defaultMonth = months.find(m => m.id === currentMonth);
if (defaultMonth) {
this.setState({ selectedValue: defaultMonth });
// Notify parent component
if (onChange) {
onChange(null, defaultMonth, name);
}
}
};
// Sync value dengan months array untuk dapat referensi yang sama
syncValueWithMonths = (value) => {
if (!value) {
this.setState({ selectedValue: null });
return;
}
const { months } = this.state;
// Cari objek yang id-nya sama dari months array
const matchedMonth = months.find(m => m.id === value.id);
if (matchedMonth) {
// Set dengan objek dari months array (referensi yang sama)
this.setState({ selectedValue: matchedMonth });
} else {
// Fallback: set value as-is
this.setState({ selectedValue: value });
}
};
generateMonthOptions = () => {
const {
monthFormat = 'short',
locale = 'en-US',
startMonth = 1,
showAllMonths = true,
filterMonths,
} = this.props;
const months = [];
const date = new Date();
const totalMonths = showAllMonths ? 12 : (12 - startMonth + 1);
for (let i = 0; i < totalMonths; i++) {
const monthNumber = startMonth + i;
if (monthNumber > 12) break;
date.setMonth(monthNumber - 1);
let monthName;
switch (monthFormat) {
case 'full':
monthName = date.toLocaleString(locale, { month: 'long' });
break;
case 'numeric':
monthName = String(monthNumber).padStart(2, '0');
break;
case 'short':
default:
monthName = date.toLocaleString(locale, { month: 'short' });
}
months.push({
id: monthNumber,
name: monthName,
});
}
const filteredMonths = filterMonths ? months.filter(filterMonths) : months;
this.setState({ months: filteredMonths });
};
handleChange = (event, newValue) => {
const { onChange, name, onMonthChange } = this.props;
this.setState({ selectedValue: newValue });
if (onChange) {
onChange(event, newValue, name);
}
if (onMonthChange) {
onMonthChange(newValue ? newValue.id : null);
}
};
getMonthName = (monthNumber, format = 'short') => {
const date = new Date();
date.setMonth(monthNumber - 1);
switch (format) {
case 'full':
return date.toLocaleString(this.props.locale, { month: 'long' });
case 'numeric':
return String(monthNumber).padStart(2, '0');
default:
return date.toLocaleString(this.props.locale, { month: 'short' });
}
};
getCurrentMonth = () => {
return new Date().getMonth() + 1;
};
getSelectedMonthValue = () => {
const { selectedValue } = this.state;
return selectedValue ? selectedValue.id : null;
};
getSelectedMonthName = () => {
const { selectedValue } = this.state;
return selectedValue ? selectedValue.name : null;
};
render() {
const {
label = 'Month',
placeholder = 'Select month',
disabled = false,
required = false,
error = false,
helperText,
style = { width: 250 },
margin = 'normal',
} = this.props;
const { selectedValue, months } = this.state;
return (
<AutocompleteField
options={months}
value={selectedValue}
onChange={this.handleChange}
label={label}
placeholder={placeholder}
disabled={disabled}
required={required}
error={error}
helperText={helperText}
style={style}
margin={margin}
/>
);
}
}
DDLMonth.propTypes = {
value: PropTypes.object,
onChange: PropTypes.func,
onMonthChange: PropTypes.func,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
style: PropTypes.object,
margin: PropTypes.string,
disabled: PropTypes.bool,
required: PropTypes.bool,
error: PropTypes.bool,
helperText: PropTypes.string,
monthFormat: PropTypes.oneOf(['full', 'short', 'numeric']),
locale: PropTypes.string,
startMonth: PropTypes.number,
showAllMonths: PropTypes.bool,
disabledMonths: PropTypes.arrayOf(PropTypes.number),
filterMonths: PropTypes.func,
useCurrentMonthAsDefault: PropTypes.bool, // NEW: Enable default current month
};
DDLMonth.defaultProps = {
label: 'Month',
placeholder: 'Select month',
monthFormat: 'short',
locale: 'en-US',
startMonth: 1,
showAllMonths: true,
style: { width: 250 },
useCurrentMonthAsDefault: false, // NEW: Default is false (opt-in)
};
export default DDLMonth;
\ No newline at end of file
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AutocompleteField from '../AutocompleteField';
class DDLYear extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: props.value || null,
years: [],
};
}
componentDidMount() {
this.generateYearOptions();
// Set default value if enabled and no value provided
if (this.props.useCurrentYearAsDefault && !this.props.value) {
setTimeout(() => this.setDefaultYear(), 0);
} else if (this.props.value) {
setTimeout(() => this.syncValueWithYears(this.props.value), 0);
}
}
componentDidUpdate(prevProps, prevState) {
// Update if value prop changes
if (prevProps.value !== this.props.value) {
this.syncValueWithYears(this.props.value);
}
// Update if year range props change
if (prevProps.startYear !== this.props.startYear ||
prevProps.endYear !== this.props.endYear ||
prevProps.showDescending !== this.props.showDescending ||
prevProps.filterYears !== this.props.filterYears) {
this.generateYearOptions();
}
// Sync value after years regenerated
if (prevState.years !== this.state.years) {
if (this.props.value) {
this.syncValueWithYears(this.props.value);
} else if (this.props.useCurrentYearAsDefault) {
this.setDefaultYear();
}
}
}
// Set default year (current year)
setDefaultYear = () => {
const { years } = this.state;
const { onChange, name } = this.props;
if (years.length === 0) return;
const currentYear = new Date().getFullYear();
const defaultYear = years.find(y => Number(y.id) === currentYear);
if (defaultYear) {
this.setState({ selectedValue: defaultYear });
// Notify parent component
if (onChange) {
onChange(null, defaultYear, name);
}
}
};
// Sync value dengan years array untuk dapat referensi yang sama
syncValueWithYears = (value) => {
if (!value) {
this.setState({ selectedValue: null });
return;
}
const { years } = this.state;
// Cari objek yang id-nya sama dari years array
const matchedYear = years.find(y => y.id === value.id || String(y.id) === String(value.id));
if (matchedYear) {
// Set dengan objek dari years array (referensi yang sama)
this.setState({ selectedValue: matchedYear });
} else {
// Fallback: set value as-is
this.setState({ selectedValue: value });
}
};
generateYearOptions = () => {
const {
startYear = 2000,
endYear,
showDescending = false,
filterYears,
} = this.props;
const currentYear = new Date().getFullYear();
const actualEndYear = endYear !== undefined ? endYear : currentYear;
// Validate year range
if (startYear > actualEndYear) {
console.warn(`startYear (${startYear}) cannot be greater than endYear (${actualEndYear})`);
this.setState({ years: [] });
return;
}
// Generate years array
let years = [];
for (let year = startYear; year <= actualEndYear; year++) {
years.push({
id: String(year),
name: String(year),
});
}
// Apply filter if provided
if (filterYears && typeof filterYears === 'function') {
years = years.filter(filterYears);
}
// Sort based on preference
years.sort((a, b) => showDescending ? Number(b.id) - Number(a.id) : Number(a.id) - Number(b.id));
this.setState({ years });
};
handleChange = (event, newValue) => {
const { onChange, name, onYearChange } = this.props;
this.setState({ selectedValue: newValue });
if (onChange) {
onChange(event, newValue, name);
}
if (onYearChange) {
onYearChange(newValue ? Number(newValue.id) : null);
}
};
getCurrentYear = () => {
return new Date().getFullYear();
};
getYearsCount = () => {
const { years } = this.state;
return years.length;
};
getSelectedYearValue = () => {
const { selectedValue } = this.state;
return selectedValue ? Number(selectedValue.id) : null;
};
render() {
const {
label = 'Year',
placeholder = 'Select year',
disabled = false,
required = false,
error = false,
helperText,
style = { width: 250 },
margin = 'normal',
} = this.props;
const { selectedValue, years } = this.state;
return (
<AutocompleteField
options={years}
value={selectedValue}
onChange={this.handleChange}
label={label}
placeholder={placeholder}
disabled={disabled}
required={required}
error={error}
helperText={helperText}
style={style}
margin={margin}
/>
);
}
}
DDLYear.propTypes = {
value: PropTypes.object,
onChange: PropTypes.func,
onYearChange: PropTypes.func,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
style: PropTypes.object,
margin: PropTypes.string,
disabled: PropTypes.bool,
required: PropTypes.bool,
error: PropTypes.bool,
helperText: PropTypes.string,
startYear: PropTypes.number,
endYear: PropTypes.number,
showDescending: PropTypes.bool,
filterYears: PropTypes.func,
useCurrentYearAsDefault: PropTypes.bool, // NEW: Enable default current year
};
DDLYear.defaultProps = {
label: 'Year',
placeholder: 'Select year',
startYear: 2000,
showDescending: false,
style: { width: 250 },
useCurrentYearAsDefault: false, // NEW: Default is false (opt-in)
};
export default DDLYear;
\ No newline at end of file
import React from 'react';
import { Typography } from '@material-ui/core';
const Header = ({
title = 'Dashboard Financial',
bgColor = 'main-color',
height = 78,
style = {},
}) => {
return (
<>
<div
className={bgColor}
style={{
height,
display: 'flex',
alignItems: 'center',
paddingLeft: 20,
...style.header,
}}
>
<Typography style={{ fontSize: '16px', color: 'white' }}>
{title}
</Typography>
</div>
</>
);
};
export default Header;
\ No newline at end of file
import React, { Component } from 'react';
import PropagateLoader from 'react-spinners/PropagateLoader';
class OverlayLoader extends Component {
render() {
const {
isLoading,
loaderSize = 20,
loaderColor = '#274B80',
overlayColor = 'rgba(255,255,255,0.8)',
customStyles = {},
} = this.props;
if (!isLoading) return null;
const defaultStyle = {
position: 'absolute',
zIndex: 110,
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: overlayColor,
};
const finalStyle = { ...defaultStyle, ...customStyles };
return (
<div style={finalStyle}>
<PropagateLoader
size={loaderSize}
color={loaderColor}
loading={isLoading}
/>
</div>
);
}
}
export default OverlayLoader;
\ No newline at end of file
import React from 'react';
import { Typography } from '@material-ui/core';
const SectionHeader = ({
title,
subtitle,
// Styling props
border = true,
borderColor = '#c4c4c4',
borderWidth = '1px',
borderStyle = 'solid',
// Typography styling
titleFontSize = '12px',
titleColor = '#4b4b4b',
titleMargin = '10px',
titleFontWeight = 'normal',
subtitleFontSize = '10px',
subtitleColor = '#666',
subtitleMargin = '5px 10px',
// Container styling
containerStyle = {},
titleStyle = {},
subtitleStyle = {},
// Children
children,
}) => {
borderStyle = border
? {
borderBottom: `${borderWidth} ${borderStyle} ${borderColor}`,
}
: {};
return (
<div style={{ ...borderStyle, ...containerStyle }}>
{/* Title */}
{title && (
<Typography
style={{
fontSize: titleFontSize,
color: titleColor,
margin: titleMargin,
fontWeight: titleFontWeight,
...titleStyle,
}}
>
{title}
</Typography>
)}
{/* Subtitle */}
{subtitle && (
<Typography
style={{
fontSize: subtitleFontSize,
color: subtitleColor,
margin: subtitleMargin,
...subtitleStyle,
}}
>
{subtitle}
</Typography>
)}
{/* Custom children */}
{children}
</div>
);
};
export default SectionHeader;
\ No newline at end of file
export function titleCase(text) {
var value = String(text).replace(/\./g, ' ')
.replace(/\s/g, ' ')
.replace(/^(.)/, function($1) { return $1.toUpperCase(); })
.replace(/^(.)/, function ($1) { return $1.toUpperCase(); })
// .replace(/\s(.)/g, function($1) { return $1.toUpperCase(); })
return value
......@@ -13,6 +13,17 @@ export function roundMath(number, decimalPlaces) {
}
export function fixNumber(num, decimalCount = 2) {
const output = Math.round((num + Number.EPSILON) * (Math.pow(10,decimalCount))) / (Math.pow(10,decimalCount))
const output = Math.round((num + Number.EPSILON) * (Math.pow(10, decimalCount))) / (Math.pow(10, decimalCount))
return output
}
export function downloadFileBlob(fileName, blobData) {
const url = window.URL.createObjectURL(blobData)
const a = document.createElement('a')
a.href = url
a.download = fileName
document.body.appendChild(a)
a.click()
a.remove()
window.URL.revokeObjectURL(url)
}
\ No newline at end of file
......@@ -26,6 +26,7 @@ import ReportCafrm from "../container/ReportCarfm/RepotrCafrm";
import Maintenance from "../container/Auth/Maintenance";
import DownloadReport from "../container/DownloadReport/DownloadReport"
import ChcmDocument from '../container/CHCM/ChcmDocument';
import ReportHistorical from '../container/Reports/ReportHistorical';
const routes = [
{
......@@ -148,6 +149,10 @@ const routes = [
path: "/home/report-trec",
main: ChcmDocument
},
{
path: "/home/historical",
main: ReportHistorical
},
{
path: "*",
main: screen404
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment