From 79cd4f9dcb9ffa0fe805a9fd3d204a67c24fd166 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 25 Sep 2025 14:59:08 +0700 Subject: [PATCH] Profit Loss Report --- .../apps/report/profit-loss/page.tsx | 58 ++++- .../profit-loss/ReportProfitLossCard.tsx | 176 ++++++++------ .../profit-loss/ReportProfitLossContent.tsx | 229 +++++++++++------- 3 files changed, 298 insertions(+), 165 deletions(-) diff --git a/src/app/[lang]/(dashboard)/(private)/apps/report/profit-loss/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/report/profit-loss/page.tsx index 93c519a..0894d00 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/report/profit-loss/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/report/profit-loss/page.tsx @@ -1,22 +1,72 @@ +'use client' + import ReportTitle from '@/components/report/ReportTitle' import ReportProfitLossCard from '@/views/apps/report/profit-loss/ReportProfitLossCard' import ReportProfitLossContent from '@/views/apps/report/profit-loss/ReportProfitLossContent' import Grid from '@mui/material/Grid2' +import { CircularProgress, Box } from '@mui/material' +import { useState } from 'react' +import { useProfitLossAnalytics } from '@/services/queries/analytics' +import { formatDateDDMMYYYY } from '@/utils/transform' +import Loading from '@/components/layout/shared/Loading' + +const ProfitLossPage = () => { + const today = new Date() + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) + const [startDate, setStartDate] = useState(monthAgo) + const [endDate, setEndDate] = useState(today) + + // Single API call at parent level + const { + data: profitData, + isLoading, + error + } = useProfitLossAnalytics({ + date_from: formatDateDDMMYYYY(startDate!), + date_to: formatDateDDMMYYYY(endDate!) + }) + + // Handle loading state + if (isLoading) { + return + } + + // Handle error state + if (error) { + return ( + + + + + + + Error loading data: {error.message} + + + + ) + } -const ProfiltLossPage = () => { return ( - + - + ) } -export default ProfiltLossPage +export default ProfitLossPage diff --git a/src/views/apps/report/profit-loss/ReportProfitLossCard.tsx b/src/views/apps/report/profit-loss/ReportProfitLossCard.tsx index a114637..503916f 100644 --- a/src/views/apps/report/profit-loss/ReportProfitLossCard.tsx +++ b/src/views/apps/report/profit-loss/ReportProfitLossCard.tsx @@ -1,89 +1,105 @@ -// MUI Imports import Grid from '@mui/material/Grid2' - -// Type Imports import type { UserDataType } from '@components/card-statistics/HorizontalWithSubtitle' - -// Component Imports import HorizontalWithSubtitle from '@components/card-statistics/HorizontalWithSubtitle' +import { ProfitLossReport } from '@/types/services/analytic' -// Vars -const data: UserDataType[] = [ - { - title: 'Pendapatan', - stats: '29.004.775', - avatarIcon: 'tabler-trending-down', - avatarColor: 'error', - trend: 'negative', - trendNumber: '48,8%', - subtitle: 'vs Bulan Lalu' - }, - { - title: 'Margin Laba Bersih', - stats: '38%', - avatarIcon: 'tabler-gauge', - avatarColor: 'success', - trend: 'positive', - trendNumber: 'Bulan Ini', - subtitle: 'Bulan Ini' - }, - { - title: 'Laba Kotor', - stats: '21.076.389', - avatarIcon: 'tabler-trending-down', - avatarColor: 'error', - trend: 'negative', - trendNumber: '43,5%', - subtitle: 'vs bulan lalu' - }, - { - title: 'Laba Bersih', - stats: '11.111.074', - avatarIcon: 'tabler-trending-down', - avatarColor: 'error', - trend: 'negative', - trendNumber: '36,8%', - subtitle: 'vs bulan lalu' - }, - { - title: 'Margin Laba Kotor', - stats: '73%', - avatarIcon: 'tabler-gauge', - avatarColor: 'success', - trend: 'positive', - trendNumber: 'Bulan Ini', - subtitle: 'Bulan Ini' - }, - { - title: 'Biaya Operasional', - stats: '9.965.315', - avatarIcon: 'tabler-trending-down', - avatarColor: 'error', - trend: 'negative', - trendNumber: '49,4%', - subtitle: 'vs Bulan Lalu' - }, - { - title: 'Rasio Biaya Operasional', - stats: '61,7%', - avatarIcon: 'tabler-gauge', - avatarColor: 'success', - trend: 'positive', - trendNumber: 'Bulan Ini', - subtitle: 'Bulan Ini' - }, - { - title: 'EBITDA', - stats: '11.032.696', - avatarIcon: 'tabler-trending-down', - avatarColor: 'error', - trend: 'negative', - trendNumber: '37,3%', - subtitle: 'vs bulan lalu' +// Utility functions +const formatIDR = (amount: number) => { + return new Intl.NumberFormat('id-ID', { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(amount) +} + +const formatPercentage = (value: number) => { + return `${value.toFixed(1)}%` +} + +interface ReportProfitLossCardProps { + profitData: ProfitLossReport | undefined +} + +const ReportProfitLossCard = ({ profitData }: ReportProfitLossCardProps) => { + if (!profitData) { + return null // Will be handled by parent loading state } -] -const ReportProfitLossCard = () => { + // Using actual data from API response with correct field names + const data: UserDataType[] = [ + { + title: 'Pendapatan', + stats: formatIDR(profitData.summary.total_revenue), + avatarIcon: 'tabler-trending-up', + avatarColor: 'success', + trend: 'positive', + trendNumber: 'Current Period', + subtitle: 'Total Revenue' + }, + { + title: 'Margin Laba Bersih', + stats: formatPercentage(profitData.summary.net_profit_margin), + avatarIcon: 'tabler-gauge', + avatarColor: profitData.summary.net_profit_margin >= 0 ? 'success' : 'error', + trend: profitData.summary.net_profit_margin >= 0 ? 'positive' : 'negative', + trendNumber: 'Current Period', + subtitle: 'Net Profit Margin' + }, + { + title: 'Laba Kotor', + stats: formatIDR(profitData.summary.gross_profit), + avatarIcon: 'tabler-trending-up', + avatarColor: profitData.summary.gross_profit >= 0 ? 'success' : 'error', + trend: profitData.summary.gross_profit >= 0 ? 'positive' : 'negative', + trendNumber: 'Current Period', + subtitle: 'Gross Profit' + }, + { + title: 'Laba Bersih', + stats: formatIDR(profitData.summary.net_profit), + avatarIcon: profitData.summary.net_profit >= 0 ? 'tabler-trending-up' : 'tabler-trending-down', + avatarColor: profitData.summary.net_profit >= 0 ? 'success' : 'error', + trend: profitData.summary.net_profit >= 0 ? 'positive' : 'negative', + trendNumber: 'Current Period', + subtitle: 'Net Profit' + }, + { + title: 'Margin Laba Kotor', + stats: formatPercentage(profitData.summary.gross_profit_margin), + avatarIcon: 'tabler-gauge', + avatarColor: profitData.summary.gross_profit_margin >= 0 ? 'success' : 'error', + trend: profitData.summary.gross_profit_margin >= 0 ? 'positive' : 'negative', + trendNumber: 'Current Period', + subtitle: 'Gross Profit Margin' + }, + { + title: 'Total Cost', + stats: formatIDR(profitData.summary.total_cost), + avatarIcon: 'tabler-trending-down', + avatarColor: 'error', + trend: 'negative', + trendNumber: 'Current Period', + subtitle: 'Total Cost' + }, + { + title: 'Tax', + stats: formatIDR(profitData.summary.total_tax), + avatarIcon: 'tabler-receipt-tax', + avatarColor: 'warning', + trend: 'neutral', + trendNumber: 'Current Period', + subtitle: 'Total Tax' + }, + { + title: 'Total Orders', + stats: profitData.summary.total_orders.toString(), + avatarIcon: 'tabler-shopping-cart', + avatarColor: 'info', + trend: 'positive', + trendNumber: 'Current Period', + subtitle: 'Total Orders' + } + ] + return ( {data.map((item, i) => ( diff --git a/src/views/apps/report/profit-loss/ReportProfitLossContent.tsx b/src/views/apps/report/profit-loss/ReportProfitLossContent.tsx index 8bb09ee..8d84ee6 100644 --- a/src/views/apps/report/profit-loss/ReportProfitLossContent.tsx +++ b/src/views/apps/report/profit-loss/ReportProfitLossContent.tsx @@ -2,12 +2,38 @@ import DateRangePicker from '@/components/RangeDatePicker' import { ReportItem, ReportItemFooter, ReportItemHeader, ReportItemSubheader } from '@/components/report/ReportItem' -import { Button, Card, CardContent, Paper } from '@mui/material' -import { useState } from 'react' +import { ProfitLossReport } from '@/types/services/analytic' +import { Button, Card, CardContent, Box } from '@mui/material' -const ReportProfitLossContent = () => { - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) +interface ReportProfitLossContentProps { + profitData: ProfitLossReport | undefined + startDate: Date | null + endDate: Date | null + onStartDateChange: (date: Date | null) => void + onEndDateChange: (date: Date | null) => void +} + +// Utility function to format date for display +const formatDisplayDate = (dateString: string) => { + const date = new Date(dateString) + return date.toLocaleDateString('id-ID', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) +} + +const ReportProfitLossContent = ({ + profitData, + startDate, + endDate, + onStartDateChange, + onEndDateChange +}: ReportProfitLossContentProps) => { + const handleExport = () => { + // TODO: Implement export functionality + console.log('Export data:', profitData) + } return ( @@ -18,97 +44,138 @@ const ReportProfitLossContent = () => { variant='tonal' startIcon={} className='max-sm:is-full' + onClick={handleExport} + disabled={!profitData} > Ekspor - - - {}} /> - - {}} - /> - {}} /> - {}} - /> - - + {profitData ? ( + <> + {/* Summary Section */} + + + {}} + /> - - {}} /> - {}} /> - - + + - - + + {}} + /> + + - - - {}} /> - {}} - /> - {}} /> - {}} - /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} /> - {}} - /> - {}} /> - {}} /> - {}} /> + + - - {}} - /> - {}} /> - {}} /> - {}} /> - - + {/* Daily Data Breakdown Section */} + {profitData.data && profitData.data.length > 0 && ( + <> + + - + {profitData.data.map((dailyData, index) => ( +
+ + + {}} + /> + + {}} /> + + {}} + /> + + {}} /> + + {}} + /> + + {}} + /> + + +
+ ))} + + + + )} + + {/* Operational Costs Section */} + + + + {}} /> + {}} + /> + + + + + + + ) : ( + + No data available + + )}
)