519 lines
15 KiB
TypeScript
Raw Normal View History

2025-08-11 22:14:26 +07:00
import React, { ReactNode } from 'react'
import {
Card,
CardContent,
CardHeader,
Button,
Typography,
FormControl,
FormControlLabel,
Radio,
RadioGroup,
TextField,
Box,
Chip,
Divider,
Grid,
useTheme
} from '@mui/material'
import { styled } from '@mui/material/styles'
// Type definitions
interface DateRange {
startDate: Date
endDate: Date
}
interface QuickAction {
label: string
handler: () => void
}
interface CustomQuickActions {
single?: QuickAction[]
range?: QuickAction[]
}
interface Labels {
filterLabel?: string
singleDateLabel?: string
rangeDateLabel?: string
selectDateLabel?: string
fromLabel?: string
toLabel?: string
todayLabel?: string
yesterdayLabel?: string
last7DaysLabel?: string
last30DaysLabel?: string
periodLabel?: string
exportHelpText?: string
}
interface ReportGeneratorProps {
// Required props
reportTitle: string
filterType: 'single' | 'range'
selectedDate: Date
dateRange: DateRange
onFilterTypeChange: (filterType: 'single' | 'range') => void
onSingleDateChange: (date: Date) => void
onDateRangeChange: (dateRange: DateRange) => void
onGeneratePDF: () => void
// Optional props dengan default values
maxWidth?: string
showQuickActions?: boolean
customQuickActions?: CustomQuickActions | null
periodFormat?: string
downloadButtonText?: string
cardShadow?: string
primaryColor?: string
// Optional helper functions
formatDateForInput?: ((date: Date) => string) | null
getReportPeriodText?: (() => string) | null
// Additional customization
className?: string
style?: React.CSSProperties
children?: ReactNode
// Loading state
isGenerating?: boolean
// Custom labels
labels?: Labels
}
// Custom styled components yang responsif terhadap theme
const StyledCard = styled(Card)(({ theme }) => ({
2025-09-25 13:08:31 +07:00
margin: '0 0 24px',
2025-08-11 22:14:26 +07:00
boxShadow: theme.palette.mode === 'dark' ? '0 2px 10px rgba(20, 21, 33, 0.3)' : '0 2px 10px rgba(58, 53, 65, 0.1)',
borderRadius: '8px',
backgroundColor: theme.palette.mode === 'dark' ? theme.palette.background.paper : '#ffffff'
}))
const PurpleButton = styled(Button)(({ theme }) => ({
backgroundColor: '#36175e',
color: 'white',
textTransform: 'none',
fontWeight: 500,
padding: '8px 24px',
'&:hover': {
backgroundColor: '#2d1350',
opacity: 0.9
},
'&:disabled': {
backgroundColor: theme.palette.mode === 'dark' ? '#444' : '#ccc',
color: theme.palette.mode === 'dark' ? '#888' : '#666'
}
}))
const QuickActionButton = styled(Button)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.08)' : '#f8f7fa',
color: theme.palette.mode === 'dark' ? theme.palette.text.secondary : '#6f6b7d',
textTransform: 'none',
fontSize: '0.875rem',
padding: '6px 16px',
borderRadius: '6px',
'&:hover': {
backgroundColor: theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.16)' : '#ebe9f1'
}
}))
const InfoBox = styled(Box)(({ theme }) => ({
marginTop: theme.spacing(3),
padding: theme.spacing(2),
backgroundColor: theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.04)' : '#f8f7fa',
borderRadius: theme.shape.borderRadius,
border: theme.palette.mode === 'dark' ? '1px solid rgba(231, 227, 252, 0.12)' : 'none'
}))
const ReportGeneratorComponent: React.FC<ReportGeneratorProps> = ({
// Required props
reportTitle,
filterType,
selectedDate,
dateRange,
onFilterTypeChange,
onSingleDateChange,
onDateRangeChange,
onGeneratePDF,
// Optional props dengan default values
maxWidth = '1024px',
showQuickActions = true,
customQuickActions = null,
periodFormat = 'id-ID',
downloadButtonText = 'Download PDF',
cardShadow,
primaryColor = '#36175e',
// Optional helper functions
formatDateForInput = null,
getReportPeriodText = null,
// Additional customization
className = '',
style = {},
children = null,
// Loading state
isGenerating = false,
// Custom labels
labels = {
filterLabel: 'Filter Tanggal:',
singleDateLabel: 'Hari Tunggal',
rangeDateLabel: 'Rentang Tanggal',
selectDateLabel: 'Pilih Tanggal:',
fromLabel: 'Dari:',
toLabel: 'Sampai:',
todayLabel: 'Hari Ini',
yesterdayLabel: 'Kemarin',
last7DaysLabel: '7 Hari Terakhir',
last30DaysLabel: '30 Hari Terakhir',
periodLabel: 'Periode:',
exportHelpText: 'Klik tombol download untuk mengeksport laporan ke PDF'
}
}) => {
const theme = useTheme()
// Dynamic colors berdasarkan theme
const textPrimary = theme.palette.text.primary
const textSecondary = theme.palette.text.secondary
const dynamicCardShadow =
cardShadow ||
(theme.palette.mode === 'dark' ? '0 2px 10px rgba(20, 21, 33, 0.3)' : '0 2px 10px rgba(58, 53, 65, 0.1)')
// Default format date function
const defaultFormatDateForInput = (date: Date): string => {
return date.toISOString().split('T')[0]
}
// Default period text function
const defaultGetReportPeriodText = (): string => {
if (filterType === 'single') {
return selectedDate.toLocaleDateString(periodFormat, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})
} else {
return `${dateRange.startDate.toLocaleDateString(periodFormat)} - ${dateRange.endDate.toLocaleDateString(periodFormat)}`
}
}
// Use provided functions or defaults
const formatDate = formatDateForInput || defaultFormatDateForInput
const getPeriodText = getReportPeriodText || defaultGetReportPeriodText
// Quick action handlers
const handleToday = (): void => {
onSingleDateChange(new Date())
}
const handleYesterday = (): void => {
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
onSingleDateChange(yesterday)
}
const handleLast7Days = (): void => {
const today = new Date()
const weekAgo = new Date()
weekAgo.setDate(today.getDate() - 6)
onDateRangeChange({ startDate: weekAgo, endDate: today })
}
const handleLast30Days = (): void => {
const today = new Date()
const monthAgo = new Date()
monthAgo.setDate(today.getDate() - 29)
onDateRangeChange({ startDate: monthAgo, endDate: today })
}
// Default quick actions
const defaultQuickActions: CustomQuickActions = {
single: [
{ label: labels.todayLabel || 'Hari Ini', handler: handleToday },
{ label: labels.yesterdayLabel || 'Kemarin', handler: handleYesterday }
],
range: [
{ label: labels.last7DaysLabel || '7 Hari Terakhir', handler: handleLast7Days },
{ label: labels.last30DaysLabel || '30 Hari Terakhir', handler: handleLast30Days }
]
}
const quickActions = customQuickActions || defaultQuickActions
return (
<StyledCard
className={className}
style={{
...style,
maxWidth,
boxShadow: dynamicCardShadow
}}
>
<CardHeader
title={
<Typography
variant='h4'
sx={{
fontWeight: 600,
color: textPrimary
}}
>
Generator {reportTitle}
</Typography>
}
action={
<PurpleButton
onClick={onGeneratePDF}
variant='contained'
disabled={isGenerating}
sx={{ backgroundColor: primaryColor }}
>
{isGenerating ? 'Generating...' : downloadButtonText}
</PurpleButton>
}
sx={{ pb: 2 }}
/>
<CardContent>
<Divider sx={{ mb: 3 }} />
{/* Filter Controls */}
<Box sx={{ mb: 3 }}>
<Typography
variant='subtitle2'
sx={{
mb: 2,
fontWeight: 600,
color: textPrimary
}}
>
{labels.filterLabel || 'Filter Tanggal:'}
</Typography>
<FormControl component='fieldset' sx={{ mb: 3 }}>
<RadioGroup
row
value={filterType}
onChange={e => onFilterTypeChange(e.target.value as 'single' | 'range')}
sx={{ gap: 3 }}
>
<FormControlLabel
value='single'
control={
<Radio
sx={{
color: primaryColor,
'&.Mui-checked': { color: primaryColor }
}}
/>
}
label={
<Typography variant='body2' sx={{ color: textSecondary }}>
{labels.singleDateLabel || 'Hari Tunggal'}
</Typography>
}
/>
<FormControlLabel
value='range'
control={
<Radio
sx={{
color: primaryColor,
'&.Mui-checked': { color: primaryColor }
}}
/>
}
label={
<Typography variant='body2' sx={{ color: textSecondary }}>
{labels.rangeDateLabel || 'Rentang Tanggal'}
</Typography>
}
/>
</RadioGroup>
</FormControl>
{/* Single Date Filter */}
{filterType === 'single' && (
<Grid container spacing={2} alignItems='center'>
<Grid item xs={12} sm='auto'>
<Typography
variant='subtitle2'
sx={{
fontWeight: 600,
color: textPrimary
}}
>
{labels.selectDateLabel || 'Pilih Tanggal:'}
</Typography>
</Grid>
<Grid item xs={12} sm='auto'>
<TextField
type='date'
value={formatDate(selectedDate)}
onChange={e => onSingleDateChange(new Date(e.target.value))}
size='small'
sx={{
'& .MuiOutlinedInput-root': {
'&.Mui-focused fieldset': {
borderColor: primaryColor
},
'& fieldset': {
borderColor: theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.22)' : theme.palette.divider
}
},
'& .MuiInputBase-input': {
color: textPrimary
}
}}
/>
</Grid>
{showQuickActions && (
<Grid item xs={12} sm='auto'>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{quickActions.single?.map((action, index) => (
<QuickActionButton key={index} onClick={action.handler}>
{action.label}
</QuickActionButton>
))}
</Box>
</Grid>
)}
</Grid>
)}
{/* Date Range Filter */}
{filterType === 'range' && (
<Grid container spacing={2} alignItems='center'>
<Grid item xs={12} sm='auto'>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography
variant='subtitle2'
sx={{
fontWeight: 600,
color: textPrimary
}}
>
{labels.fromLabel || 'Dari:'}
</Typography>
<TextField
type='date'
value={formatDate(dateRange.startDate)}
onChange={e =>
onDateRangeChange({
...dateRange,
startDate: new Date(e.target.value)
})
}
size='small'
sx={{
'& .MuiOutlinedInput-root': {
'&.Mui-focused fieldset': {
borderColor: primaryColor
},
'& fieldset': {
borderColor:
theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.22)' : theme.palette.divider
}
},
'& .MuiInputBase-input': {
color: textPrimary
}
}}
/>
</Box>
</Grid>
<Grid item xs={12} sm='auto'>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography
variant='subtitle2'
sx={{
fontWeight: 600,
color: textPrimary
}}
>
{labels.toLabel || 'Sampai:'}
</Typography>
<TextField
type='date'
value={formatDate(dateRange.endDate)}
onChange={e =>
onDateRangeChange({
...dateRange,
endDate: new Date(e.target.value)
})
}
size='small'
sx={{
'& .MuiOutlinedInput-root': {
'&.Mui-focused fieldset': {
borderColor: primaryColor
},
'& fieldset': {
borderColor:
theme.palette.mode === 'dark' ? 'rgba(231, 227, 252, 0.22)' : theme.palette.divider
}
},
'& .MuiInputBase-input': {
color: textPrimary
}
}}
/>
</Box>
</Grid>
{showQuickActions && (
<Grid item xs={12} sm='auto'>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{quickActions.range?.map((action, index) => (
<QuickActionButton key={index} onClick={action.handler}>
{action.label}
</QuickActionButton>
))}
</Box>
</Grid>
)}
</Grid>
)}
</Box>
<InfoBox>
2025-08-14 03:15:47 +07:00
<Typography variant='body2' component='div' sx={{ color: textSecondary }}>
2025-08-11 22:14:26 +07:00
{labels.periodLabel || 'Periode:'}{' '}
<Chip
label={getPeriodText()}
size='small'
sx={{
backgroundColor: primaryColor,
color: 'white',
fontWeight: 500,
ml: 1
}}
/>
</Typography>
<Typography
variant='body2'
sx={{
color: textSecondary,
mt: 1
}}
>
{labels.exportHelpText || 'Klik tombol download untuk mengeksport laporan ke PDF'}
</Typography>
</InfoBox>
{/* Custom content dapat ditambahkan di sini */}
{children}
</CardContent>
</StyledCard>
)
}
export default ReportGeneratorComponent