diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/analytics/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/analytics/page.tsx deleted file mode 100644 index d11cf1d..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/analytics/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// MUI Imports -import Grid from '@mui/material/Grid2' - -// Components Imports -import WebsiteAnalyticsSlider from '@views/dashboards/analytics/WebsiteAnalyticsSlider' -import LineAreaDailySalesChart from '@views/dashboards/analytics/LineAreaDailySalesChart' -import SalesOverview from '@views/dashboards/analytics/SalesOverview' -import EarningReports from '@views/dashboards/analytics/EarningReports' -import SupportTracker from '@views/dashboards/analytics/SupportTracker' -import SalesByCountries from '@views/dashboards/analytics/SalesByCountries' -import TotalEarning from '@views/dashboards/analytics/TotalEarning' -import MonthlyCampaignState from '@views/dashboards/analytics/MonthlyCampaignState' -import SourceVisits from '@views/dashboards/analytics/SourceVisits' -import ProjectsTable from '@views/dashboards/analytics/ProjectsTable' - -/** - * ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the - * ! `.env` file found at root of your project and also update the API endpoints like `/pages/profile` in below example. - * ! Also, remove the above server action import and the action itself from the `src/app/server/actions.ts` file to clean up unused code - * ! because we've used the server action for getting our static data. - */ - -/* const getProfileData = async () => { - // Vars - const res = await fetch(`${process.env.API_URL}/pages/profile`) - - if (!res.ok) { - throw new Error('Failed to fetch profileData') - } - - return res.json() -} */ - -const DashboardAnalytics = async () => { - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} - -export default DashboardAnalytics diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/crm/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/crm/page.tsx deleted file mode 100644 index 2d43907..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/crm/page.tsx +++ /dev/null @@ -1,356 +0,0 @@ -'use client' - -import React, { useEffect, useState } from 'react' -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - BarChart, - Bar, - PieChart, - Pie, - Cell -} from 'recharts' -import { useSalesAnalytics } from '../../../../../../services/queries/analytics' -import Loading from '../../../../../../components/layout/shared/Loading' -import PickerBasic from '../../../../../../components/date-picker/PickerBasic' -import { formatDateDDMMYYYY } from '../../../../../../utils/transform' - -// Tabler icons component -const TablerIcon = ({ name = '', className = '' }) => - -const DashboardCRM = () => { - const today = new Date() - const monthAgo = new Date() - monthAgo.setDate(today.getDate() - 30) - - const [dateFrom, setDateFrom] = useState(monthAgo) - const [dateTo, setDateTo] = useState(today) - - const { data: analytics, isLoading } = useSalesAnalytics({ - date_from: formatDateDDMMYYYY(dateFrom!), - date_to: formatDateDDMMYYYY(dateTo!) - }) - - useEffect(() => { - if (analytics) { - setDateFrom(new Date(analytics.date_from)) - setDateTo(new Date(analytics.date_to)) - } - }, [analytics]) - - // Format currency - const formatCurrency = (amount: any) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', - minimumFractionDigits: 0 - }).format(amount) - } - - // Format date - const formatDate = (dateString: any) => { - return new Date(dateString).toLocaleDateString('id-ID', { - month: 'short', - day: 'numeric' - }) - } - - // Format long date - const formatLongDate = (dateString: any) => { - return new Date(dateString).toLocaleDateString('id-ID', { - year: 'numeric', - month: 'long', - day: 'numeric' - }) - } - - // Prepare chart data - const chartData = analytics?.data.map((item: any) => ({ - date: formatDate(item.date), - sales: item.sales, - orders: item.orders, - items: item.items, - averageOrderValue: item.orders > 0 ? Math.round(item.sales / item.orders) : 0 - })) - - // Colors for charts - const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'] - - // Metric cards data - const metrics = [ - { - title: 'Total Sales', - value: formatCurrency(analytics?.summary.total_sales), - icon: 'currency-dollar', - color: 'text-green-600', - bgColor: 'bg-green-50', - change: '+12.5%', - changeColor: 'text-green-600' - }, - { - title: 'Total Orders', - value: analytics?.summary.total_orders.toLocaleString(), - icon: 'shopping-cart', - color: 'text-blue-600', - bgColor: 'bg-blue-50', - change: '+8.2%', - changeColor: 'text-blue-600' - }, - { - title: 'Total Items', - value: analytics?.summary.total_items.toLocaleString(), - icon: 'package', - color: 'text-purple-600', - bgColor: 'bg-purple-50', - change: '+15.1%', - changeColor: 'text-purple-600' - }, - { - title: 'Average Order Value', - value: formatCurrency(analytics?.summary.average_order_value), - icon: 'trending-up', - color: 'text-orange-600', - bgColor: 'bg-orange-50', - change: '+4.3%', - changeColor: 'text-orange-600' - } - ] - - // Performance data for pie chart - const performanceData = analytics?.data.map((item: any, index: any) => ({ - name: formatDate(item.date), - value: item.sales, - color: colors[index % colors.length] - })) - - if (isLoading) return - - return ( -
- {/* Header */} -
-
-

Sales Analytics Dashboard

- -
-
-
- - Grouped by {analytics?.group_by} -
-
- - {analytics?.data.length} data points -
-
-
- - {/* Metrics Cards */} -
- {metrics.map((metric, index) => ( -
-
-
-
-

{metric.title}

-

{metric.value}

-
- - {metric.change} -
-
-
- -
-
-
-
- ))} -
- - {/* Charts Section */} -
- {/* Sales Trend Chart */} -
-
-
- -

Sales Trend

-
-
- - - - - `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' /> - [ - name === 'sales' ? formatCurrency(value) : value.toLocaleString(), - name === 'sales' ? 'Sales' : name === 'orders' ? 'Orders' : 'Items' - ]} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - - -
-
-
- - {/* Sales Distribution */} -
-
-
- -

Sales Distribution

-
-
- - - `${name} ${(percent * 100).toFixed(0)}%`} - > - {performanceData?.map((entry: any, index: any) => ( - - ))} - - [formatCurrency(value), 'Sales']} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - -
-
-
-
- - {/* Orders & Items Chart */} -
-
-
- -

Orders & Items Analysis

-
-
- - - - - - - - - - -
-
-
- - {/* Detailed Data Table */} -
-
-
- -

Daily Performance Details

-
-
- - - - - - - - - - - - - {analytics?.data.map((row: any, index: any) => ( - - - - - - - - - ))} - -
DateSalesOrdersItemsAvg Order ValueNet Sales
{formatLongDate(row.date)}{formatCurrency(row.sales)}{row.orders.toLocaleString()}{row.items.toLocaleString()} - {formatCurrency(row.orders > 0 ? Math.round(row.sales / row.orders) : 0)} - - {formatCurrency(row.net_sales)} -
-
-
-
- - {/* Summary Footer */} -
-
-
-
-

{formatCurrency(analytics?.summary.net_sales)}

-

Net Sales

-
-
-

{analytics?.summary.total_orders}

-

Total Orders

-
-
-

{formatCurrency(analytics?.summary.total_tax)}

-

Total Tax

-
-
-

- {formatCurrency(analytics?.summary.total_discount)} -

-

Total Discount

-
-
-
-
-
- ) -} - -export default DashboardCRM diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/order/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/order/page.tsx deleted file mode 100644 index d37d8c3..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/order/page.tsx +++ /dev/null @@ -1,348 +0,0 @@ -'use client' - -import React, { useEffect, useState } from 'react' -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - BarChart, - Bar, - PieChart, - Pie, - Cell -} from 'recharts' -import PickerBasic from '../../../../../../../components/date-picker/PickerBasic' -import Loading from '../../../../../../../components/layout/shared/Loading' -import { useSalesAnalytics } from '../../../../../../../services/queries/analytics' -import { formatDateDDMMYYYY } from '../../../../../../../utils/transform' - -// Tabler icons component -const TablerIcon = ({ name = '', className = '' }) => - -const EcomerceOrderReport = () => { - const today = new Date() - const monthAgo = new Date() - monthAgo.setDate(today.getDate() - 30) - - const [dateFrom, setDateFrom] = useState(monthAgo) - const [dateTo, setDateTo] = useState(today) - - const { data: analytics, isLoading } = useSalesAnalytics({ - date_from: formatDateDDMMYYYY(dateFrom!), - date_to: formatDateDDMMYYYY(dateTo!) - }) - - useEffect(() => { - if (analytics) { - setDateFrom(new Date(analytics.date_from)) - setDateTo(new Date(analytics.date_to)) - } - }, [analytics]) - - // Format currency - const formatCurrency = (amount: any) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', - minimumFractionDigits: 0 - }).format(amount) - } - - // Format date - const formatDate = (dateString: any) => { - return new Date(dateString).toLocaleDateString('id-ID', { - month: 'short', - day: 'numeric' - }) - } - - // Format long date - const formatLongDate = (dateString: any) => { - return new Date(dateString).toLocaleDateString('id-ID', { - year: 'numeric', - month: 'long', - day: 'numeric' - }) - } - - // Prepare chart data - const chartData = analytics?.data.map((item: any) => ({ - date: formatDate(item.date), - sales: item.sales, - orders: item.orders, - items: item.items, - averageOrderValue: item.orders > 0 ? Math.round(item.sales / item.orders) : 0 - })) - - // Colors for charts - const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'] - - // Metric cards data - const metrics = [ - { - title: 'Total Sales', - value: formatCurrency(analytics?.summary.total_sales), - icon: 'currency-dollar', - color: 'text-green-600', - bgColor: 'bg-green-50', - change: '+12.5%', - changeColor: 'text-green-600' - }, - { - title: 'Total Orders', - value: analytics?.summary.total_orders.toLocaleString(), - icon: 'shopping-cart', - color: 'text-blue-600', - bgColor: 'bg-blue-50', - change: '+8.2%', - changeColor: 'text-blue-600' - }, - { - title: 'Total Items', - value: analytics?.summary.total_items.toLocaleString(), - icon: 'package', - color: 'text-purple-600', - bgColor: 'bg-purple-50', - change: '+15.1%', - changeColor: 'text-purple-600' - }, - { - title: 'Average Order Value', - value: formatCurrency(analytics?.summary.average_order_value), - icon: 'trending-up', - color: 'text-orange-600', - bgColor: 'bg-orange-50', - change: '+4.3%', - changeColor: 'text-orange-600' - } - ] - - // Performance data for pie chart - const performanceData = analytics?.data.map((item: any, index: any) => ({ - name: formatDate(item.date), - value: item.sales, - color: colors[index % colors.length] - })) - - if (isLoading) return - - return ( -
- {/* Header */} -
-
-

Sales Analytics

-
-
- -
-
- - {/* Metrics Cards */} -
- {metrics.map((metric, index) => ( -
-
-
-
-

{metric.title}

-

{metric.value}

-
- - {metric.change} -
-
-
- -
-
-
-
- ))} -
- - {/* Charts Section */} -
- {/* Sales Trend Chart */} -
-
-
- -

Sales Trend

-
-
- - - - - `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' /> - [ - name === 'sales' ? formatCurrency(value) : value.toLocaleString(), - name === 'sales' ? 'Sales' : name === 'orders' ? 'Orders' : 'Items' - ]} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - - -
-
-
- - {/* Sales Distribution */} -
-
-
- -

Sales Distribution

-
-
- - - `${name} ${(percent * 100).toFixed(0)}%`} - > - {performanceData?.map((entry: any, index: any) => ( - - ))} - - [formatCurrency(value), 'Sales']} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - -
-
-
-
- - {/* Orders & Items Chart */} -
-
-
- -

Orders & Items Analysis

-
-
- - - - - - - - - - -
-
-
- - {/* Detailed Data Table */} -
-
-
- -

Daily Performance Details

-
-
- - - - - - - - - - - - - {analytics?.data.map((row: any, index: any) => ( - - - - - - - - - ))} - -
DateSalesOrdersItemsAvg Order ValueNet Sales
{formatLongDate(row.date)}{formatCurrency(row.sales)}{row.orders.toLocaleString()}{row.items.toLocaleString()} - {formatCurrency(row.orders > 0 ? Math.round(row.sales / row.orders) : 0)} - - {formatCurrency(row.net_sales)} -
-
-
-
- - {/* Summary Footer */} -
-
-
-
-

{formatCurrency(analytics?.summary.net_sales)}

-

Net Sales

-
-
-

{analytics?.summary.total_orders}

-

Total Orders

-
-
-

{formatCurrency(analytics?.summary.total_tax)}

-

Total Tax

-
-
-

- {formatCurrency(analytics?.summary.total_discount)} -

-

Total Discount

-
-
-
-
-
- ) -} - -export default EcomerceOrderReport diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/page.tsx deleted file mode 100644 index acbe92f..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import EcommerceDashboard from '../../apps/ecommerce/dashboard/page' - -const DashboardECommerce = () => { - return -} - -export default DashboardECommerce diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/product/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/product/page.tsx deleted file mode 100644 index 24b0fbe..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/ecommerce/product/page.tsx +++ /dev/null @@ -1,397 +0,0 @@ -'use client' - -import React, { useState, useEffect } from 'react' -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - PieChart, - Pie, - Cell, - LineChart, - Line -} from 'recharts' -import { useProductSalesAnalytics } from '../../../../../../../services/queries/analytics' -import Loading from '../../../../../../../components/layout/shared/Loading' -import { formatDateDDMMYYYY } from '../../../../../../../utils/transform' -import PickerBasic from '../../../../../../../components/date-picker/PickerBasic' - -// Tabler icons component -const TablerIcon = ({ name = '', className = '' }) => - -const EcomerceProductReport = () => { - const today = new Date() - const monthAgo = new Date() - monthAgo.setDate(today.getDate() - 30) - - const [dateFrom, setDateFrom] = useState(monthAgo) - const [dateTo, setDateTo] = useState(today) - const [selectedCategory, setSelectedCategory] = useState('all') - const [sortBy, setSortBy] = useState('revenue') - const [sortOrder, setSortOrder] = useState('desc') - - const { data: analytics, isLoading } = useProductSalesAnalytics({ - date_from: formatDateDDMMYYYY(dateFrom!), - date_to: formatDateDDMMYYYY(dateTo!) - }) - - useEffect(() => { - if (analytics) { - setDateFrom(new Date(analytics.date_from)) - setDateTo(new Date(analytics.date_to)) - } - }, [analytics]) - - // Format currency - const formatCurrency = (amount: any) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', - minimumFractionDigits: 0 - }).format(amount) - } - - // Calculate summary metrics - const summary = { - totalProducts: analytics?.data.length, - totalQuantitySold: analytics?.data.reduce((sum, item) => sum + item.quantity_sold, 0), - totalRevenue: analytics?.data.reduce((sum, item) => sum + item.revenue, 0), - totalOrders: analytics?.data.reduce((sum, item) => sum + item.order_count, 0), - averageOrderValue: analytics?.data - ? analytics!.data.reduce((sum, item) => sum + item.revenue, 0) / - analytics!.data.reduce((sum, item) => sum + item.order_count, 0) - : 0 - } - - // Get unique categories - const categories = ['all', ...new Set(analytics?.data.map(item => item.category_name))] - - // Filter and sort data - const filteredData = analytics?.data - .filter(item => selectedCategory === 'all' || item.category_name === selectedCategory) - .sort((a: any, b: any) => { - const multiplier = sortOrder === 'desc' ? -1 : 1 - return (a[sortBy] - b[sortBy]) * multiplier - }) - - // Prepare chart data - const chartData = filteredData?.slice(0, 10).map(item => ({ - name: item.product_name.length > 15 ? item.product_name.substring(0, 15) + '...' : item.product_name, - fullName: item.product_name, - revenue: item.revenue, - quantity: item.quantity_sold, - orders: item.order_count, - avgPrice: item.average_price - })) - - // Category performance data - const categoryData = categories - .filter(cat => cat !== 'all') - .map(category => { - const categoryItems = analytics?.data.filter(item => item.category_name === category) - return { - name: category, - revenue: categoryItems?.reduce((sum, item) => sum + item.revenue, 0), - quantity: categoryItems?.reduce((sum, item) => sum + item.quantity_sold, 0), - products: categoryItems?.length - } - }) - - // Colors for charts - const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#06B6D4', '#84CC16', '#F97316'] - - // Metric cards data - const metrics = [ - { - title: 'Total Revenue', - value: formatCurrency(summary.totalRevenue), - icon: 'currency-dollar', - color: 'text-green-600', - bgColor: 'bg-green-50', - subtitle: 'From all products' - }, - { - title: 'Products Sold', - value: summary.totalQuantitySold?.toLocaleString(), - icon: 'package', - color: 'text-blue-600', - bgColor: 'bg-blue-50', - subtitle: `${summary.totalProducts} different products` - }, - { - title: 'Total Orders', - value: summary.totalOrders?.toLocaleString(), - icon: 'shopping-cart', - color: 'text-purple-600', - bgColor: 'bg-purple-50', - subtitle: 'Completed orders' - }, - { - title: 'Avg Order Value', - value: formatCurrency(summary.averageOrderValue || 0), - icon: 'trending-up', - color: 'text-orange-600', - bgColor: 'bg-orange-50', - subtitle: 'Per order average' - } - ] - - const handleSort = (field: any) => { - if (sortBy === field) { - setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc') - } else { - setSortBy(field) - setSortOrder('desc') - } - } - - if (isLoading) return - - return ( -
- {/* Header */} -
-
-

Product Analytics

-
- - {/* Filters */} -
- -
-
- - {/* Summary Cards */} -
- {metrics.map((metric, index) => ( -
-
-
-
-

{metric.title}

-

{metric.value}

-

{metric.subtitle}

-
-
- -
-
-
-
- ))} -
- - {/* Charts Section */} -
- {/* Product Revenue Chart */} -
-
-
- -

Top Products by Revenue

-
-
- - - - - `${(value / 1000).toFixed(0)}K`} stroke='#6b7280' /> - [ - name === 'revenue' ? formatCurrency(value) : value.toLocaleString(), - name === 'revenue' ? 'Revenue' : name === 'quantity' ? 'Quantity Sold' : 'Orders' - ]} - labelFormatter={label => { - const item = chartData?.find(d => d.name === label) - return item ? item.fullName : label - }} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - - -
-
-
- - {/* Category Distribution */} -
-
-
- -

Category Distribution

-
-
- - - `${name} ${(percent * 100).toFixed(0)}%`} - > - {categoryData.map((entry, index) => ( - - ))} - - [formatCurrency(value), 'Revenue']} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - -
-
-
-
- - {/* Quantity vs Orders Chart */} -
-
-
- -

Quantity Sold vs Order Count

-
-
- - - - - - { - const item = chartData?.find(d => d.name === label) - return item ? item.fullName : label - }} - contentStyle={{ - backgroundColor: '#fff', - border: '1px solid #e5e7eb', - borderRadius: '8px', - boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' - }} - /> - - - - -
-
-
- - {/* Detailed Products Table */} -
-
-
-
- -

Product Performance Details

-
-
Showing {filteredData?.length} products
-
- -
- - - - - - - - - - - - - {filteredData?.map((product, index) => ( - - - - - - - - - ))} - -
ProductCategory handleSort('quantity_sold')} - > -
- Quantity Sold - -
-
handleSort('revenue')} - > -
- Revenue - -
-
handleSort('average_price')} - > -
- Avg Price - -
-
handleSort('order_count')} - > -
- Orders - -
-
-
{product.product_name}
-
- - {product.category_name} - - - {product.quantity_sold.toLocaleString()} - - {formatCurrency(product.revenue)} - {formatCurrency(product.average_price)}{product.order_count.toLocaleString()}
-
-
-
-
- ) -} - -export default EcomerceProductReport diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/finance/payment-method/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/finance/payment-method/page.tsx deleted file mode 100644 index aa9778c..0000000 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/finance/payment-method/page.tsx +++ /dev/null @@ -1,355 +0,0 @@ -import React from 'react'; - -const TablerIcon = ({ name = '', className = '' }) => ; - -const PaymentMethodAnalytics = () => { - // Default data structure for demonstration - const defaultData = { - "organization_id": "4ecf5cfb-9ac1-4fbd-94e4-9f149f07460a", - "outlet_id": "d5b38fc4-8df7-4d54-99e2-b8825977f5ca", - "date_from": "2025-07-05T00:00:00+07:00", - "date_to": "2025-08-05T23:59:59.999999999+07:00", - "group_by": "day", - "summary": { - "total_amount": 212000, - "total_orders": 6, - "total_payments": 6, - "average_order_value": 35333.333333333336 - }, - "data": [ - { - "payment_method_id": "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905", - "payment_method_name": "CASH", - "payment_method_type": "cash", - "total_amount": 212000, - "order_count": 6, - "payment_count": 6, - "percentage": 100 - } - ] - }; - - const analyticsData = defaultData; - const { summary, data: paymentMethods } = analyticsData; - - // Format currency - const formatCurrency = (amount: any) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', - minimumFractionDigits: 0, - maximumFractionDigits: 0 - }).format(amount); - }; - - // Format date - const formatDate = (dateString: any) => { - return new Date(dateString).toLocaleDateString('id-ID', { - year: 'numeric', - month: 'long', - day: 'numeric' - }); - }; - - // Get icon for payment method - const getPaymentMethodIcon = (type: any) => { - switch (type.toLowerCase()) { - case 'cash': - return ; - case 'card': - case 'credit_card': - case 'debit_card': - return ; - default: - return ; - } - }; - - // Get color classes for payment method type - const getPaymentMethodColors = (type: any) => { - switch (type.toLowerCase()) { - case 'cash': - return { - chip: 'bg-green-100 text-green-800 border-green-200', - progress: 'bg-green-500' - }; - case 'card': - case 'credit_card': - case 'debit_card': - return { - chip: 'bg-blue-100 text-blue-800 border-blue-200', - progress: 'bg-blue-500' - }; - default: - return { - chip: 'bg-gray-100 text-gray-800 border-gray-200', - progress: 'bg-gray-500' - }; - } - }; - - // Progress Bar Component - const ProgressBar = ({ value, className, color }: any) => { - return ( -
-
-
- ); - }; - - // Chip Component - const Chip = ({ label, colorClasses }: any) => { - return ( - - {label} - - ); - }; - - return ( -
- {/* Header */} -
-
- -
-
-

- Payment Analytics -

-
- -

- {formatDate(analyticsData.date_from)} - {formatDate(analyticsData.date_to)} -

-
-
-
- - {/* Summary Cards */} -
-
-
-
-
-

- Total Revenue -

-

- {formatCurrency(summary.total_amount)} -

-
-
- -
-
-
-
- -
-
-
-
-

- Total Orders -

-

- {summary.total_orders.toLocaleString()} -

-
-
- -
-
-
-
- -
-
-
-
-

- Total Payments -

-

- {summary.total_payments.toLocaleString()} -

-
-
- -
-
-
-
- -
-
-
-
-

- Avg Order Value -

-

- {formatCurrency(summary.average_order_value)} -

-
-
- -
-
-
-
-
- - {/* Payment Methods Breakdown */} -
-
-
- -

- Payment Methods Breakdown -

-
- -
- - - - - - - - - - - - - - {paymentMethods.map((method, index) => { - const colors = getPaymentMethodColors(method.payment_method_type); - return ( - - - - - - - - - - ); - })} - -
Payment MethodTypeAmountOrdersPaymentsPercentageUsage
-
- {getPaymentMethodIcon(method.payment_method_type)} - - {method.payment_method_name} - -
-
- - - {formatCurrency(method.total_amount)} - - {method.order_count.toLocaleString()} - - {method.payment_count.toLocaleString()} - - - {method.percentage.toFixed(1)}% - - -
- - - {method.percentage.toFixed(1)}% - -
-
-
- - {paymentMethods.length === 0 && ( -
- -

- No payment method data available for the selected period. -

-
- )} -
-
- - {/* Additional Stats */} -
-
-

- Key Insights -

-
-
-
-
- - - Most Used Payment Method - -
- - {paymentMethods.length > 0 ? paymentMethods[0].payment_method_name : 'N/A'} - -
-
-
- - - Revenue per Payment - -
- - {formatCurrency(summary.total_payments > 0 ? summary.total_amount / summary.total_payments : 0)} - -
-
-
-
-
- - - Payment Success Rate - -
- - {((summary.total_payments / summary.total_orders) * 100).toFixed(1)}% - -
-
-
- - - Payment Methods Used - -
- - {paymentMethods.length} - -
-
-
-
-
-
- ); -}; - -export default PaymentMethodAnalytics; diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/orders/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/orders/page.tsx new file mode 100644 index 0000000..0028b4b --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/orders/page.tsx @@ -0,0 +1,116 @@ +'use client' + +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Component Imports +import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' +import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' + +// Server Action Imports +import Loading from '../../../../../../components/layout/shared/Loading' +import { useSalesAnalytics } from '../../../../../../services/queries/analytics' + +const DashboardOrder = () => { + const { data, isLoading } = useSalesAnalytics() + + const formatDate = (dateString: any) => { + return new Date(dateString).toLocaleDateString('id-ID', { + month: 'short', + day: 'numeric' + }) + } + + const transformSalesData = (data: any) => { + return [ + { + type: 'items', + avatarIcon: 'tabler-shopping-cart', + date: data.map((d: any) => formatDate(d.date)), + series: [{ data: data.map((d: any) => d.items) }] + }, + { + type: 'orders', + avatarIcon: 'tabler-chart-bar', + date: data.map((d: any) => formatDate(d.date)), + series: [{ data: data.map((d: any) => d.orders) }] + }, + { + type: 'sales', + avatarIcon: 'tabler-currency-dollar', + date: data.map((d: any) => formatDate(d.date)), + series: [{ data: data.map((d: any) => d.sales) }] + } + ] + } + + if (isLoading) return + + return ( + + + + + + + + + + + + + + + + + {/* + + + + + + + + + + + + + + + + + */} + + ) +} + +export default DashboardOrder diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/overview/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/overview/page.tsx new file mode 100644 index 0000000..bfed1f3 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/overview/page.tsx @@ -0,0 +1,99 @@ +'use client' + +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Component Imports +import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' +import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' + +// Server Action Imports +import Loading from '../../../../../../components/layout/shared/Loading' +import { useDashboardAnalytics } from '../../../../../../services/queries/analytics' +import { DashboardReport, PaymentDataItem, ProductData, RecentSale } from '../../../../../../types/services/analytic' + +const DashboardOverview = () => { + const { data, isLoading } = useDashboardAnalytics() + + const formatDate = (dateString: any) => { + return new Date(dateString).toLocaleDateString('id-ID', { + month: 'short', + day: 'numeric' + }) + } + + const transformSalesData = (data: DashboardReport) => { + return [ + { + type: 'products', + avatarIcon: 'tabler-package', + date: data.top_products.map((d: ProductData) => d.product_name), + series: [{ data: data.top_products.map((d: ProductData) => d.revenue) }] + }, + { + type: 'orders', + avatarIcon: 'tabler-shopping-cart', + date: data.recent_sales.map((d: RecentSale) => formatDate(d.date)), + series: [{ data: data.recent_sales.map((d: RecentSale) => d.net_sales) }] + }, + { + type: 'payments', + avatarIcon: 'tabler-credit-card-pay', + date: data.payment_methods.map((d: PaymentDataItem) => d.payment_method_name), + series: [{ data: data.payment_methods.map((d: PaymentDataItem) => d.total_amount) }] + }, + ] + } + + if (isLoading) return + + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default DashboardOverview diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/payment-methods/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/payment-methods/page.tsx new file mode 100644 index 0000000..cf4c6ef --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/payment-methods/page.tsx @@ -0,0 +1,98 @@ +'use client' + +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Component Imports +import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' +import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' + +// Server Action Imports +import Loading from '../../../../../../components/layout/shared/Loading' +import { usePaymentAnalytics } from '../../../../../../services/queries/analytics' +import { PaymentDataItem } from '../../../../../../types/services/analytic' + +const DashboardPayment = () => { + const { data, isLoading } = usePaymentAnalytics() + + const transformSalesData = (data: PaymentDataItem[]) => { + return [ + { + type: 'payment', + avatarIcon: 'tabler-package', + date: data.map((d: PaymentDataItem) => d.payment_method_name), + series: [{ data: data.map((d: PaymentDataItem) => d.total_amount) }] + } + ] + } + + if (isLoading) return + + return ( + + + + + + + + + + + + + + + + + {/* + + + + + + + + + + + + + + + + + */} + + ) +} + +export default DashboardPayment diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/products/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/products/page.tsx new file mode 100644 index 0000000..463e39d --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/products/page.tsx @@ -0,0 +1,115 @@ +'use client' + +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Component Imports +import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' +import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' + +// Server Action Imports +import Loading from '../../../../../../components/layout/shared/Loading' +import { useProductSalesAnalytics } from '../../../../../../services/queries/analytics' + +const DashboardProduct = () => { + const { data, isLoading } = useProductSalesAnalytics() + + const summary = { + totalProducts: data?.data.length, + totalQuantitySold: data?.data.reduce((sum, item) => sum + item.quantity_sold, 0), + totalRevenue: data?.data.reduce((sum, item) => sum + item.revenue, 0), + totalOrders: data?.data.reduce((sum, item) => sum + item.order_count, 0), + averageOrderValue: data?.data + ? data!.data.reduce((sum, item) => sum + item.revenue, 0) / + data!.data.reduce((sum, item) => sum + item.order_count, 0) + : 0 + } + + const formatDate = (dateString: any) => { + return new Date(dateString).toLocaleDateString('id-ID', { + month: 'short', + day: 'numeric' + }) + } + + const transformSalesData = (data: any) => { + return [ + { + type: 'products', + avatarIcon: 'tabler-package', + date: data.map((d: any) => d.product_name), + series: [{ data: data.map((d: any) => d.revenue) }] + } + ] + } + + if (isLoading) return + + return ( + + + + + + + + + + + + + + + + + {/* + + + + + + + + + + + + + + + + + */} + + ) +} + +export default DashboardProduct diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx new file mode 100644 index 0000000..eba1a15 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx @@ -0,0 +1,117 @@ +'use client' + +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Component Imports +import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' +import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' + +// Server Action Imports +import Loading from '../../../../../../components/layout/shared/Loading' +import { useProfitLossAnalytics } from '../../../../../../services/queries/analytics' +import { + ProductDataReport, + ProfitLossReport +} from '../../../../../../types/services/analytic' + +function formatMetricName(metric: string): string { + const nameMap: { [key: string]: string } = { + revenue: 'Revenue', + cost: 'Cost', + gross_profit: 'Gross Profit', + gross_profit_margin: 'Gross Profit Margin (%)', + tax: 'Tax', + discount: 'Discount', + net_profit: 'Net Profit', + net_profit_margin: 'Net Profit Margin (%)', + orders: 'Orders' + } + + return nameMap[metric] || metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) +} + +const DashboardProfitLoss = () => { + const { data, isLoading } = useProfitLossAnalytics() + + const formatDate = (dateString: any) => { + return new Date(dateString).toLocaleDateString('id-ID', { + month: 'short', + day: 'numeric' + }) + } + + const metrics = ['revenue', 'cost', 'gross_profit', 'net_profit'] + + const transformSalesData = (data: ProfitLossReport) => { + return [ + { + type: 'products', + avatarIcon: 'tabler-package', + date: data.product_data.map((d: ProductDataReport) => d.product_name), + series: [{ data: data.product_data.map((d: ProductDataReport) => d.revenue) }] + }, + // { + // type: 'profits', + // avatarIcon: 'tabler-currency-dollar', + // date: data.data.map((d: DailyData) => formatDate(d.date)), + // series: metrics.map(metric => ({ + // name: formatMetricName(metric as string), + // data: data.data.map((item: any) => item[metric] as number) + // })) + // } + ] + } + + if (isLoading) return + + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default DashboardProfitLoss diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 33173b7..609adb9 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -81,17 +81,11 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { menuSectionStyles={menuSectionStyles(verticalNavOptions, theme)} > }> - {dictionary['navigation'].crm} - {dictionary['navigation'].analytics} - - {dictionary['navigation'].orders} - {dictionary['navigation'].products} - - - - {dictionary['navigation'].paymentMethods} - - + {dictionary['navigation'].overview} + {dictionary['navigation'].profitloss} + {dictionary['navigation'].products} + {dictionary['navigation'].orders} + {dictionary['navigation'].paymentMethods} }> diff --git a/src/data/dictionaries/ar.json b/src/data/dictionaries/ar.json index a9c0fbf..abe3fb1 100644 --- a/src/data/dictionaries/ar.json +++ b/src/data/dictionaries/ar.json @@ -21,6 +21,8 @@ "add": "يضيف", "addjustment": "تعديل", "category": "فئة", + "overview": "نظرة عامة", + "profitloss": "الربح والخسارة", "finance": "مالية", "paymentMethods": "طرق الدفع", "organization": "المنظمة", diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index 4bb761b..3498401 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -21,6 +21,8 @@ "add": "Add", "addjustment": "Addjustment", "category": "Category", + "overview": "Overview", + "profitloss": "Profit Loss", "units": "Units", "finance": "Finance", "paymentMethods": "Payment Methods", diff --git a/src/data/dictionaries/fr.json b/src/data/dictionaries/fr.json index 369c180..6b71d4c 100644 --- a/src/data/dictionaries/fr.json +++ b/src/data/dictionaries/fr.json @@ -21,6 +21,8 @@ "add": "Ajouter", "addjustment": "Ajustement", "category": "Catégorie", + "overview": "Aperçu", + "profitloss": "Profit et perte", "finance": "Finance", "paymentMethods": "Méthodes de paiement", "organization": "Organisation", diff --git a/src/redux-store/index.ts b/src/redux-store/index.ts index 7981376..6421eaa 100644 --- a/src/redux-store/index.ts +++ b/src/redux-store/index.ts @@ -4,12 +4,14 @@ import { configureStore } from '@reduxjs/toolkit' import productReducer from '@/redux-store/slices/product' import customerReducer from '@/redux-store/slices/customer' import paymentMethodReducer from '@/redux-store/slices/paymentMethod' +import ingredientReducer from '@/redux-store/slices/ingredient' export const store = configureStore({ reducer: { productReducer, customerReducer, - paymentMethodReducer + paymentMethodReducer, + ingredientReducer }, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }) }) diff --git a/src/redux-store/slices/ingredient.ts b/src/redux-store/slices/ingredient.ts new file mode 100644 index 0000000..700e180 --- /dev/null +++ b/src/redux-store/slices/ingredient.ts @@ -0,0 +1,52 @@ +// Third-party Imports +import type { PayloadAction } from '@reduxjs/toolkit' +import { createSlice } from '@reduxjs/toolkit' + +// Type Imports + +// Data Imports +import { IngredientItem } from '../../types/services/ingredient' + +const initialState: { currentIngredient: IngredientItem } = { + currentIngredient: { + id: '', + organization_id: '', + outlet_id: '', + name: '', + unit_id: '', + cost: 0, + stock: 0, + is_semi_finished: false, + is_active: true, + metadata: {}, + created_at: '', + updated_at: '', + unit: { + id: '', + organization_id: '', + outlet_id: '', + name: '', + abbreviation: '', + is_active: false, + created_at: '', + updated_at: '' + } + } +} + +export const ingredientSlice = createSlice({ + name: 'ingredient', + initialState, + reducers: { + setIngredient: (state, action: PayloadAction) => { + state.currentIngredient = action.payload + }, + resetIngredient: state => { + state.currentIngredient = initialState.currentIngredient + } + } +}) + +export const { setIngredient, resetIngredient } = ingredientSlice.actions + +export default ingredientSlice.reducer diff --git a/src/services/mutations/ingredients.ts b/src/services/mutations/ingredients.ts index 0486f8a..16d164f 100644 --- a/src/services/mutations/ingredients.ts +++ b/src/services/mutations/ingredients.ts @@ -1,52 +1,52 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' -import { CustomerRequest } from '../../types/services/customer' -import { api } from '../api' import { toast } from 'react-toastify' +import { IngredientRequest } from '../../types/services/ingredient' +import { api } from '../api' export const useIngredientsMutation = () => { const queryClient = useQueryClient() - const createCustomer = useMutation({ - mutationFn: async (newCustomer: CustomerRequest) => { - const response = await api.post('/customers', newCustomer) + const createIngredient = useMutation({ + mutationFn: async (newIngredient: IngredientRequest) => { + const response = await api.post('/ingredients', newIngredient) return response.data }, onSuccess: () => { - toast.success('Customer created successfully!') - queryClient.invalidateQueries({ queryKey: ['customers'] }) + toast.success('Ingredient created successfully!') + queryClient.invalidateQueries({ queryKey: ['ingredients'] }) }, onError: (error: any) => { toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') } }) - const updateCustomer = useMutation({ - mutationFn: async ({ id, payload }: { id: string; payload: CustomerRequest }) => { - const response = await api.put(`/customers/${id}`, payload) + const updateIngredient = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: IngredientRequest }) => { + const response = await api.put(`/ingredients/${id}`, payload) return response.data }, onSuccess: () => { - toast.success('Customer updated successfully!') - queryClient.invalidateQueries({ queryKey: ['customers'] }) + toast.success('Ingredient updated successfully!') + queryClient.invalidateQueries({ queryKey: ['ingredients'] }) }, onError: (error: any) => { toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') } }) - const deleteCustomer = useMutation({ + const deleteIngredient = useMutation({ mutationFn: async (id: string) => { - const response = await api.delete(`/customers/${id}`) + const response = await api.delete(`/ingredients/${id}`) return response.data }, onSuccess: () => { - toast.success('Customer deleted successfully!') - queryClient.invalidateQueries({ queryKey: ['customers'] }) + toast.success('Ingredient deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['ingredients'] }) }, onError: (error: any) => { toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') } }) - return { createCustomer, updateCustomer, deleteCustomer } + return { createIngredient, updateIngredient, deleteIngredient } } diff --git a/src/services/queries/analytics.ts b/src/services/queries/analytics.ts index 36825a9..27e30bf 100644 --- a/src/services/queries/analytics.ts +++ b/src/services/queries/analytics.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query' -import { ProductSalesReport, SalesReport } from '../../types/services/analytic' +import { DashboardReport, PaymentReport, ProductSalesReport, ProfitLossReport, SalesReport } from '../../types/services/analytic' import { api } from '../api' import { formatDateDDMMYYYY } from '../../utils/transform' @@ -10,11 +10,11 @@ interface AnalyticQueryParams { export function useSalesAnalytics(params: AnalyticQueryParams = {}) { const today = new Date() - const sevenDaysAgo = new Date() - sevenDaysAgo.setDate(today.getDate() - 30) + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) const defaultDateTo = formatDateDDMMYYYY(today) - const defaultDateFrom = formatDateDDMMYYYY(sevenDaysAgo) + const defaultDateFrom = formatDateDDMMYYYY(monthAgo) const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params @@ -40,11 +40,11 @@ export function useSalesAnalytics(params: AnalyticQueryParams = {}) { export function useProductSalesAnalytics(params: AnalyticQueryParams = {}) { const today = new Date() - const sevenDaysAgo = new Date() - sevenDaysAgo.setDate(today.getDate() - 30) + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) const defaultDateTo = formatDateDDMMYYYY(today) - const defaultDateFrom = formatDateDDMMYYYY(sevenDaysAgo) + const defaultDateFrom = formatDateDDMMYYYY(monthAgo) const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params @@ -67,3 +67,93 @@ export function useProductSalesAnalytics(params: AnalyticQueryParams = {}) { } }) } + +export function usePaymentAnalytics(params: AnalyticQueryParams = {}) { + const today = new Date() + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) + + const defaultDateTo = formatDateDDMMYYYY(today) + const defaultDateFrom = formatDateDDMMYYYY(monthAgo) + + const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params + + return useQuery({ + queryKey: ['analytics-payment-methods', { date_from, date_to, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('date_from', date_from) + queryParams.append('date_to', date_to) + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/analytics/payment-methods?${queryParams.toString()}`) + return res.data.data + } + }) +} + +export function useDashboardAnalytics(params: AnalyticQueryParams = {}) { + const today = new Date() + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) + + const defaultDateTo = formatDateDDMMYYYY(today) + const defaultDateFrom = formatDateDDMMYYYY(monthAgo) + + const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params + + return useQuery({ + queryKey: ['analytics-dashboard', { date_from, date_to, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('date_from', date_from) + queryParams.append('date_to', date_to) + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/analytics/dashboard?${queryParams.toString()}`) + return res.data.data + } + }) +} + +export function useProfitLossAnalytics(params: AnalyticQueryParams = {}) { + const today = new Date() + const monthAgo = new Date() + monthAgo.setDate(today.getDate() - 30) + + const defaultDateTo = formatDateDDMMYYYY(today) + const defaultDateFrom = formatDateDDMMYYYY(monthAgo) + + const { date_from = defaultDateFrom, date_to = defaultDateTo, ...filters } = params + + return useQuery({ + queryKey: ['analytics-profit-loss', { date_from, date_to, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('date_from', date_from) + queryParams.append('date_to', date_to) + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/analytics/profit-loss?${queryParams.toString()}`) + return res.data.data + } + }) +} diff --git a/src/types/services/analytic.ts b/src/types/services/analytic.ts index 04006b7..6f057ce 100644 --- a/src/types/services/analytic.ts +++ b/src/types/services/analytic.ts @@ -46,3 +46,112 @@ export interface ProductSalesReport { date_to: string data: ProductData[] } + +export type PaymentDataItem = { + payment_method_id: string + payment_method_name: string + payment_method_type: string + total_amount: number + order_count: number + payment_count: number + percentage: number +} + +export type SummaryData = { + total_amount: number + total_orders: number + total_payments: number + average_order_value: number +} + +export type PaymentReport = { + organization_id: string + outlet_id: string + date_from: string + date_to: string + group_by: string + summary: SummaryData + data: PaymentDataItem[] +} + +export type Overview = { + total_sales: number + total_orders: number + average_order_value: number + total_customers: number + voided_orders: number + refunded_orders: number +} + +export type RecentSale = { + date: string + sales: number + orders: number + items: number + tax: number + discount: number + net_sales: number +} + +export type DashboardReport = { + organization_id: string + outlet_id: string + date_from: string + date_to: string + overview: Overview + top_products: ProductData[] + payment_methods: PaymentDataItem[] + recent_sales: RecentSale[] +} + +export interface ProfitLossReport { + organization_id: string; + date_from: string; // ISO date string with timezone + date_to: string; // ISO date string with timezone + group_by: string; + summary: Summary; + data: DailyData[]; + product_data: ProductDataReport[]; +} + +export interface Summary { + total_revenue: number; + total_cost: number; + gross_profit: number; + gross_profit_margin: number; + total_tax: number; + total_discount: number; + net_profit: number; + net_profit_margin: number; + total_orders: number; + average_profit: number; + profitability_ratio: number; +} + +export interface DailyData { + date: string; // ISO date string with timezone + revenue: number; + cost: number; + gross_profit: number; + gross_profit_margin: number; + tax: number; + discount: number; + net_profit: number; + net_profit_margin: number; + orders: number; +} + +export interface ProductDataReport { + product_id: string; + product_name: string; + category_id: string; + category_name: string; + quantity_sold: number; + revenue: number; + cost: number; + gross_profit: number; + gross_profit_margin: number; + average_price: number; + average_cost: number; + profit_per_unit: number; +} diff --git a/src/types/services/ingredient.ts b/src/types/services/ingredient.ts index 9204cd5..eb9641a 100644 --- a/src/types/services/ingredient.ts +++ b/src/types/services/ingredient.ts @@ -36,3 +36,12 @@ export type Ingredients = { data: IngredientItem[] pagination: Pagination } + +export type IngredientRequest = { + name: string + unit_id: string + cost: number + stock: number + is_semi_finished: boolean + outlet_id: string +} diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 56cf5df..a161f9d 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -6,6 +6,15 @@ export const formatCurrency = (amount: number) => { }).format(amount) } +export const formatShortCurrency = (num: number): string => { + if (num >= 1_000_000) { + return (num / 1_000_000).toFixed(2) + 'M' + } else if (num >= 1_000) { + return (num / 1_000).toFixed(2) + 'k' + } + return num.toString() +} + export const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', @@ -17,8 +26,8 @@ export const formatDate = (dateString: string) => { } export const formatDateDDMMYYYY = (date: Date) => { - const day = String(date.getDate()).padStart(2, '0') - const month = String(date.getMonth() + 1).padStart(2, '0') - const year = date.getFullYear() - return `${day}-${month}-${year}` - } + const day = String(date.getDate()).padStart(2, '0') + const month = String(date.getMonth() + 1).padStart(2, '0') + const year = date.getFullYear() + return `${day}-${month}-${year}` +} diff --git a/src/views/apps/ecommerce/manage-reviews/ManageReviewsTable.tsx b/src/views/apps/ecommerce/manage-reviews/ManageReviewsTable.tsx deleted file mode 100644 index aee55c3..0000000 --- a/src/views/apps/ecommerce/manage-reviews/ManageReviewsTable.tsx +++ /dev/null @@ -1,413 +0,0 @@ -'use client' - -// React Imports -import { useEffect, useMemo, useState } from 'react' - -// Next Imports -import Link from 'next/link' -import { useParams } from 'next/navigation' - -// MUI Imports -import Button from '@mui/material/Button' -import Card from '@mui/material/Card' -import Checkbox from '@mui/material/Checkbox' -import Chip from '@mui/material/Chip' -import MenuItem from '@mui/material/MenuItem' -import Rating from '@mui/material/Rating' -import type { TextFieldProps } from '@mui/material/TextField' -import Typography from '@mui/material/Typography' - -// Third-party Imports -import type { RankingInfo } from '@tanstack/match-sorter-utils' -import { rankItem } from '@tanstack/match-sorter-utils' -import type { ColumnDef, FilterFn } from '@tanstack/react-table' -import { - createColumnHelper, - flexRender, - getCoreRowModel, - getFacetedMinMaxValues, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable -} from '@tanstack/react-table' -import classnames from 'classnames' - -// Type Imports -import type { ReviewType } from '@/types/apps/ecommerceTypes' -import type { Locale } from '@configs/i18n' - -// Component Imports -import CustomAvatar from '@core/components/mui/Avatar' -import CustomTextField from '@core/components/mui/TextField' -import OptionMenu from '@core/components/option-menu' - -// Util Imports -import { getLocalizedUrl } from '@/utils/i18n' - -// Style Imports -import tableStyles from '@core/styles/table.module.css' - -declare module '@tanstack/table-core' { - interface FilterFns { - fuzzy: FilterFn - } - interface FilterMeta { - itemRank: RankingInfo - } -} - -type ReviewWithActionsType = ReviewType & { - actions?: string -} - -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the itemRank info - addMeta({ - itemRank - }) - - // Return if the item should be filtered in/out - return itemRank.passed -} - -const DebouncedInput = ({ - value: initialValue, - onChange, - debounce = 500, - ...props -}: { - value: string | number - onChange: (value: string | number) => void - debounce?: number -} & Omit) => { - // States - const [value, setValue] = useState(initialValue) - - useEffect(() => { - setValue(initialValue) - }, [initialValue]) - - useEffect(() => { - const timeout = setTimeout(() => { - onChange(value) - }, debounce) - - return () => clearTimeout(timeout) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]) - - return setValue(e.target.value)} /> -} - -// Column Definitions -const columnHelper = createColumnHelper() - -const ManageReviewsTable = ({ reviewsData }: { reviewsData?: ReviewType[] }) => { - // States - const [status, setStatus] = useState('All') - const [rowSelection, setRowSelection] = useState({}) - const [allData, setAllData] = useState(...[reviewsData]) - const [data, setData] = useState(allData) - const [globalFilter, setGlobalFilter] = useState('') - - // Hooks - const { lang: locale } = useParams() - - const columns = useMemo[]>( - () => [ - { - id: 'select', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - - ) - }, - columnHelper.accessor('product', { - header: 'Product', - cell: ({ row }) => ( -
- -
- - {row.original.product} - - - {row.original.companyName} - -
-
- ) - }), - columnHelper.accessor('reviewer', { - header: 'Reviewer', - cell: ({ row }) => ( -
- -
- - {row.original.reviewer} - - {row.original.email} -
-
- ) - }), - columnHelper.accessor('head', { - header: 'Review', - sortingFn: (rowA, rowB) => rowA.original.review - rowB.original.review, - cell: ({ row }) => ( -
- } - /> - - {row.original.head} - - - {row.original.para} - -
- ) - }), - columnHelper.accessor('date', { - header: 'Date', - sortingFn: (rowA, rowB) => { - const dateA = new Date(rowA.original.date) - const dateB = new Date(rowB.original.date) - - return dateA.getTime() - dateB.getTime() - }, - cell: ({ row }) => { - const date = new Date(row.original.date).toLocaleDateString('en-US', { - month: 'short', - day: '2-digit', - year: 'numeric' - }) - - return {date} - } - }), - columnHelper.accessor('status', { - header: 'Status', - cell: ({ row }) => ( -
- -
- ) - }), - columnHelper.accessor('actions', { - header: 'Actions', - cell: ({ row }) => ( - setAllData(allData?.filter(review => review.id !== row.original.id)), - className: 'flex items-center' - } - } - ]} - /> - ), - enableSorting: false - }) - ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [data] - ) - - const table = useReactTable({ - data: data as ReviewType[], - columns, - filterFns: { - fuzzy: fuzzyFilter - }, - state: { - rowSelection, - globalFilter - }, - initialState: { - pagination: { - pageSize: 10 - } - }, - enableRowSelection: true, //enable row selection for all rows - // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row - globalFilterFn: fuzzyFilter, - onRowSelectionChange: setRowSelection, - getCoreRowModel: getCoreRowModel(), - onGlobalFilterChange: setGlobalFilter, - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getFacetedMinMaxValues: getFacetedMinMaxValues() - }) - - useEffect(() => { - const filteredData = allData?.filter(review => { - if (status !== 'All' && review.status !== status) return false - - return true - }) - - setData(filteredData) - }, [status, allData, setData]) - - return ( - <> - -
- setGlobalFilter(String(value))} - placeholder='Search Product' - className='max-sm:is-full' - /> -
- table.setPageSize(Number(e.target.value))} - className='sm:is-[140px] flex-auto is-full' - > - 10 - 25 - 50 - - setStatus(e.target.value)} - className='is-full sm:is-[140px] flex-auto' - > - All - Published - Pending - - -
-
-
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - {table.getFilteredRowModel().rows.length === 0 ? ( - - - - - - ) : ( - - {table - .getRowModel() - .rows.slice(0, table.getState().pagination.pageSize) - .map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ) - })} - - )} -
- {header.isPlaceholder ? null : ( - <> -
- {flexRender(header.column.columnDef.header, header.getContext())} - {{ - asc: , - desc: - }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} -
- - )} -
- No data available -
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- {/* } - count={table.getFilteredRowModel().rows.length} - rowsPerPage={table.getState().pagination.pageSize} - page={table.getState().pagination.pageIndex} - onPageChange={(_, page) => { - table.setPageIndex(page) - }} - /> */} -
- - ) -} - -export default ManageReviewsTable diff --git a/src/views/apps/ecommerce/manage-reviews/ReviewsStatistics.tsx b/src/views/apps/ecommerce/manage-reviews/ReviewsStatistics.tsx deleted file mode 100644 index dcafcc2..0000000 --- a/src/views/apps/ecommerce/manage-reviews/ReviewsStatistics.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client' - -// Next Imports -import dynamic from 'next/dynamic' - -// MUI Imports -import Grid from '@mui/material/Grid2' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import Chip from '@mui/material/Chip' -import Typography from '@mui/material/Typography' -import { useTheme } from '@mui/material/styles' - -// Third-party Imports -import type { ApexOptions } from 'apexcharts' - -// Styled Component Imports -const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) - -// Vars -const series = [{ data: [32, 52, 72, 94, 116, 94, 72] }] - -const ReviewsStatistics = () => { - // Hook - const theme = useTheme() - - // Vars - const successLightOpacity = 'var(--mui-palette-success-lightOpacity)' - - const options: ApexOptions = { - chart: { - parentHeightOffset: 0, - toolbar: { show: false } - }, - plotOptions: { - bar: { - borderRadius: 6, - distributed: true, - columnWidth: '40%' - } - }, - legend: { show: false }, - tooltip: { enabled: false }, - dataLabels: { enabled: false }, - colors: [ - successLightOpacity, - successLightOpacity, - successLightOpacity, - successLightOpacity, - 'var(--mui-palette-success-main)', - successLightOpacity, - successLightOpacity - ], - states: { - hover: { - filter: { type: 'none' } - }, - active: { - filter: { type: 'none' } - } - }, - grid: { - show: false, - padding: { - top: -30, - left: 0, - right: 0, - bottom: -12 - } - }, - xaxis: { - categories: ['M', 'T', 'W', 'T', 'F', 'S', 'S'], - axisTicks: { show: false }, - axisBorder: { show: false }, - tickPlacement: 'on', - labels: { - style: { - colors: 'var(--mui-palette-text-disabled)', - fontFamily: theme.typography.fontFamily, - fontSize: theme.typography.body2.fontSize as string - } - } - }, - yaxis: { show: false }, - responsive: [ - { - breakpoint: 600, - options: { - chart: { - width: 275 - } - } - } - ] - } - - return ( - - - - -
-
- Reviews statistics -
- 12 New reviews - -
-
-
- - 87% Positive reviews - - Weekly Report -
-
-
- - - -
-
-
- ) -} - -export default ReviewsStatistics diff --git a/src/views/apps/ecommerce/manage-reviews/TotalReviews.tsx b/src/views/apps/ecommerce/manage-reviews/TotalReviews.tsx deleted file mode 100644 index de7f350..0000000 --- a/src/views/apps/ecommerce/manage-reviews/TotalReviews.tsx +++ /dev/null @@ -1,72 +0,0 @@ -'use client' - -// MUI Imports -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import Chip from '@mui/material/Chip' -import Divider from '@mui/material/Divider' -import LinearProgress from '@mui/material/LinearProgress' -import Typography from '@mui/material/Typography' -import useMediaQuery from '@mui/material/useMediaQuery' -import { useTheme } from '@mui/material/styles' - -type DataType = { - rating: number - value: number -} - -// Vars -const totalReviewsData: DataType[] = [ - { rating: 5, value: 109 }, - { rating: 4, value: 40 }, - { rating: 3, value: 18 }, - { rating: 2, value: 12 }, - { rating: 1, value: 8 } -] - -const TotalReviews = () => { - // Hooks - const theme = useTheme() - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')) - - return ( - - -
-
-
- - 4.89 - - -
- - Total 187 reviews - - All reviews are from genuine customers - -
- -
- {totalReviewsData.map((item, index) => ( -
- - {item.rating} Star - - - {item.value} -
- ))} -
-
-
-
- ) -} - -export default TotalReviews diff --git a/src/views/apps/ecommerce/products/ingredient/AddProductIngredientDrawer.tsx b/src/views/apps/ecommerce/products/ingredient/AddProductIngredientDrawer.tsx new file mode 100644 index 0000000..033f796 --- /dev/null +++ b/src/views/apps/ecommerce/products/ingredient/AddProductIngredientDrawer.tsx @@ -0,0 +1,265 @@ +// React Imports +import { useEffect, useMemo, useState } from 'react' + +// MUI Imports +import Button from '@mui/material/Button' +import Divider from '@mui/material/Divider' +import Drawer from '@mui/material/Drawer' +import IconButton from '@mui/material/IconButton' +import Switch from '@mui/material/Switch' +import Typography from '@mui/material/Typography' + +// Third-party Imports +import PerfectScrollbar from 'react-perfect-scrollbar' + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../../../../redux-store' +import { useIngredientsMutation } from '../../../../../services/mutations/ingredients' +import { IngredientRequest } from '../../../../../types/services/ingredient' +import { Autocomplete, CircularProgress } from '@mui/material' +import { useOutlets } from '../../../../../services/queries/outlets' +import { useDebounce } from 'use-debounce' +import { useUnits } from '../../../../../services/queries/units' +import { resetIngredient } from '../../../../../redux-store/slices/ingredient' + +type Props = { + open: boolean + handleClose: () => void +} + +// Vars +const initialData = { + name: '', + unit_id: '', + cost: 0, + stock: 0, + is_semi_finished: false, + outlet_id: '' +} + +const AddProductIngredientDrawer = (props: Props) => { + const dispatch = useDispatch() + + // Props + const { open, handleClose } = props + + const { createIngredient, updateIngredient } = useIngredientsMutation() + const { currentIngredient } = useSelector((state: RootState) => state.ingredientReducer) + + // States + const [formData, setFormData] = useState(initialData) + const [outletInput, setOutletInput] = useState('') + const [outletDebouncedInput] = useDebounce(outletInput, 500) + const [unitInput, setUnitInput] = useState('') + const [unitDebouncedInput] = useDebounce(unitInput, 500) + + const { data: outlets, isLoading: outletsLoading } = useOutlets({ + search: outletDebouncedInput + }) + const { data: units, isLoading: unitsLoading } = useUnits({ + search: unitDebouncedInput + }) + + const outletOptions = useMemo(() => outlets?.outlets || [], [outlets]) + const unitOptions = useMemo(() => units?.data || [], [units]) + + useEffect(() => { + if (currentIngredient.id) { + setFormData(currentIngredient) + } + }, [currentIngredient]) + + const handleSubmit = (e: any) => { + e.preventDefault() + + const { stock, cost, ...rest } = formData + + const payload = { + ...rest, + stock: Number(stock), + cost: Number(cost) + } + + if (currentIngredient.id) { + updateIngredient.mutate( + { id: currentIngredient.id, payload }, + { + onSuccess: () => { + handleReset() + } + } + ) + } else { + createIngredient.mutate(payload, { + onSuccess: () => { + handleReset() + } + }) + } + } + + const handleReset = () => { + handleClose() + dispatch(resetIngredient()) + setFormData(initialData) + } + + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + + return ( + +
+ {currentIngredient.id ? 'Edit' : 'Add'} Ingredient + + + +
+ + +
+
+ + Basic Information + + + option.name} + value={outletOptions.find(p => p.id === formData.outlet_id) || null} + onInputChange={(event, newOutlettInput) => { + setOutletInput(newOutlettInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + outlet_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {outletsLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + option.name} + value={unitOptions.find(p => p.id === formData.unit_id) || null} + onInputChange={(event, newUnitInput) => { + setUnitInput(newUnitInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + unit_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {unitsLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + + +
+
+ + Semi Finished + +
+ setFormData({ ...formData, is_semi_finished: e.target.checked })} + /> +
+
+ + +
+ +
+
+
+ ) +} + +export default AddProductIngredientDrawer diff --git a/src/views/apps/ecommerce/products/ingredient/ProductIngredientTable.tsx b/src/views/apps/ecommerce/products/ingredient/ProductIngredientTable.tsx index 6b54716..5f9c063 100644 --- a/src/views/apps/ecommerce/products/ingredient/ProductIngredientTable.tsx +++ b/src/views/apps/ecommerce/products/ingredient/ProductIngredientTable.tsx @@ -34,6 +34,10 @@ import { useUnitsMutation } from '../../../../../services/mutations/units' import { useIngredients } from '../../../../../services/queries/ingredients' import { IngredientItem } from '../../../../../types/services/ingredient' import { formatCurrency } from '../../../../../utils/transform' +import AddProductIngredientDrawer from './AddProductIngredientDrawer' +import { useIngredientsMutation } from '../../../../../services/mutations/ingredients' +import { useDispatch } from 'react-redux' +import { setIngredient } from '../../../../../redux-store/slices/ingredient' declare module '@tanstack/table-core' { interface FilterFns { @@ -94,13 +98,15 @@ const DebouncedInput = ({ const columnHelper = createColumnHelper() const ProductIngredientTable = () => { + const dispatch = useDispatch() + // States - const [addUnitOpen, setAddUnitOpen] = useState(false) + const [addIngredientOpen, setAddIngredientOpen] = useState(false) const [editUnitOpen, setEditUnitOpen] = useState(false) const [rowSelection, setRowSelection] = useState({}) const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) - const [unitId, setUnitId] = useState('') + const [ingredientId, setIngredientId] = useState('') const [openConfirm, setOpenConfirm] = useState(false) const [search, setSearch] = useState('') @@ -111,7 +117,7 @@ const ProductIngredientTable = () => { search }) - const { mutate: deleteUnit, isPending: isDeleting } = useUnitsMutation().deleteUnit + const { deleteIngredient } = useIngredientsMutation() const ingredients = data?.data ?? [] const totalCount = data?.pagination.total_count ?? 0 @@ -128,7 +134,7 @@ const ProductIngredientTable = () => { }, []) const handleDelete = () => { - deleteUnit(unitId, { + deleteIngredient.mutate(ingredientId, { onSuccess: () => setOpenConfirm(false) }) } @@ -210,7 +216,8 @@ const ProductIngredientTable = () => {
{ - setEditUnitOpen(!editUnitOpen) + setAddIngredientOpen(!editUnitOpen) + dispatch(setIngredient(row.original)) }} > @@ -225,7 +232,7 @@ const ProductIngredientTable = () => { icon: 'tabler-trash', menuItemProps: { onClick: () => { - setUnitId(row.original.id) + setIngredientId(row.original.id) setOpenConfirm(true) } } @@ -287,7 +294,7 @@ const ProductIngredientTable = () => { -
- -
- - Share the referral link - -
- -
- - - - - - -
-
-
- - - ) -} - -export default InviteAndShare diff --git a/src/views/apps/ecommerce/referrals/ReferredUsersTable.tsx b/src/views/apps/ecommerce/referrals/ReferredUsersTable.tsx deleted file mode 100644 index 3257a44..0000000 --- a/src/views/apps/ecommerce/referrals/ReferredUsersTable.tsx +++ /dev/null @@ -1,278 +0,0 @@ -'use client' - -// React Imports -import { useMemo, useState } from 'react' - -// Next Imports -import Link from 'next/link' -import { useParams } from 'next/navigation' - -// MUI Imports -import Button from '@mui/material/Button' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import Checkbox from '@mui/material/Checkbox' -import Chip from '@mui/material/Chip' -import MenuItem from '@mui/material/MenuItem' -import Typography from '@mui/material/Typography' - -// Third-party Imports -import type { RankingInfo } from '@tanstack/match-sorter-utils' -import { rankItem } from '@tanstack/match-sorter-utils' -import type { ColumnDef, FilterFn } from '@tanstack/react-table' -import { - createColumnHelper, - flexRender, - getCoreRowModel, - getFacetedMinMaxValues, - getFacetedRowModel, - getFacetedUniqueValues, - getPaginationRowModel, - getSortedRowModel, - useReactTable -} from '@tanstack/react-table' -import classnames from 'classnames' - -// Type Imports -import type { ReferralsType } from '@/types/apps/ecommerceTypes' -import type { Locale } from '@configs/i18n' -import type { ThemeColor } from '@core/types' - -// Component Imports -import CustomAvatar from '@core/components/mui/Avatar' -import CustomTextField from '@core/components/mui/TextField' - -// Util Imports -import { getLocalizedUrl } from '@/utils/i18n' - -// Style Imports -import tableStyles from '@core/styles/table.module.css' - -declare module '@tanstack/table-core' { - interface FilterFns { - fuzzy: FilterFn - } - interface FilterMeta { - itemRank: RankingInfo - } -} - -type userStatusType = { - [key: string]: ThemeColor -} - -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the itemRank info - addMeta({ - itemRank - }) - - // Return if the item should be filtered in/out - return itemRank.passed -} - -// Vars -const userStatusObj: userStatusType = { - Rejected: 'error', - Unpaid: 'warning', - Paid: 'success' -} - -// Column Definitions -const columnHelper = createColumnHelper() - -const ReferredUsersTable = ({ referralsData }: { referralsData?: ReferralsType[] }) => { - // States - const [rowSelection, setRowSelection] = useState({}) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [data, setData] = useState(...[referralsData]) - - // Hooks - const { lang: locale } = useParams() - - const columns = useMemo[]>( - () => [ - { - id: 'select', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - - ) - }, - columnHelper.accessor('user', { - header: 'Users', - cell: ({ row }) => ( -
- -
- - {row.original.user} - - {row.original.email} -
-
- ) - }), - columnHelper.accessor('referredId', { - header: 'Referred ID', - cell: ({ row }) => {row.original.referredId} - }), - columnHelper.accessor('status', { - header: 'Status', - cell: ({ row }) => ( - - ) - }), - columnHelper.accessor('value', { - header: 'Value', - cell: ({ row }) => {row.original.value} - }), - columnHelper.accessor('earning', { - header: 'Earning', - cell: ({ row }) => {row.original.earning} - }) - ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ) - - const table = useReactTable({ - data: data as ReferralsType[], - columns, - filterFns: { - fuzzy: fuzzyFilter - }, - state: { - rowSelection - }, - initialState: { - pagination: { - pageSize: 10 - } - }, - enableRowSelection: true, //enable row selection for all rows - // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row - globalFilterFn: fuzzyFilter, - onRowSelectionChange: setRowSelection, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getFacetedMinMaxValues: getFacetedMinMaxValues() - }) - - return ( - <> - - - Referred Users -
- table.setPageSize(Number(e.target.value))} - className='flex-auto is-[70px]' - > - 10 - 25 - 50 - - -
-
-
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - {table.getFilteredRowModel().rows.length === 0 ? ( - - - - - - ) : ( - - {table - .getRowModel() - .rows.slice(0, table.getState().pagination.pageSize) - .map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ) - })} - - )} -
- {header.isPlaceholder ? null : ( - <> -
- {flexRender(header.column.columnDef.header, header.getContext())} - {{ - asc: , - desc: - }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} -
- - )} -
- No data available -
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- {/* } - count={table.getExpandedRowModel().rows.length} - rowsPerPage={table.getState().pagination.pageSize} - page={table.getState().pagination.pageIndex} - onPageChange={(_, page) => { - table.setPageIndex(page) - }} - /> */} -
- - ) -} - -export default ReferredUsersTable diff --git a/src/views/dashboards/crm/DistributedBarChartOrder.tsx b/src/views/dashboards/crm/DistributedBarChartOrder.tsx index 8effa72..9cca298 100644 --- a/src/views/dashboards/crm/DistributedBarChartOrder.tsx +++ b/src/views/dashboards/crm/DistributedBarChartOrder.tsx @@ -1,123 +1,56 @@ 'use client' // Next Imports -import dynamic from 'next/dynamic' // MUI Imports import Card from '@mui/material/Card' -import Typography from '@mui/material/Typography' import CardContent from '@mui/material/CardContent' -import CardHeader from '@mui/material/CardHeader' -import { useTheme } from '@mui/material/styles' // Third-party Imports -import type { ApexOptions } from 'apexcharts' +import classnames from 'classnames' +import CustomAvatar, { CustomAvatarProps } from '../../../@core/components/mui/Avatar' +import { ThemeColor } from '../../../@core/types' +import { Skeleton, Typography } from '@mui/material' +import { formatShortCurrency } from '../../../utils/transform' -// Styled Component Imports -const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) +type Props = { + title: string + value: number + isLoading: boolean + avatarIcon: string + avatarSkin?: CustomAvatarProps['skin'] + avatarSize?: number + avatarColor?: ThemeColor +} -// Vars -const series = [{ data: [77, 55, 23, 43, 77, 55, 89] }] +const DistributedBarChartOrder = ({ + title, + value, + isLoading, + avatarIcon, + avatarSkin, + avatarColor +}: Props) => { -const DistributedBarChartOrder = () => { - // Hooks - const theme = useTheme() - - // Vars - const actionSelectedColor = 'var(--mui-palette-action-selected)' - - const options: ApexOptions = { - chart: { - type: 'bar', - stacked: false, - parentHeightOffset: 0, - toolbar: { show: false }, - sparkline: { enabled: true } - }, - tooltip: { enabled: false }, - legend: { show: false }, - dataLabels: { enabled: false }, - colors: ['var(--mui-palette-primary-main)'], - states: { - hover: { - filter: { type: 'none' } - }, - active: { - filter: { type: 'none' } - } - }, - plotOptions: { - bar: { - borderRadius: 3, - horizontal: false, - columnWidth: '32%', - colors: { - backgroundBarRadius: 5, - backgroundBarColors: [ - actionSelectedColor, - actionSelectedColor, - actionSelectedColor, - actionSelectedColor, - actionSelectedColor - ] - } - } - }, - grid: { - show: false, - padding: { - left: -3, - right: 5, - top: 15, - bottom: 18 - } - }, - xaxis: { - labels: { show: false }, - axisTicks: { show: false }, - axisBorder: { show: false } - }, - yaxis: { show: false }, - responsive: [ - { - breakpoint: 1350, - options: { - plotOptions: { - bar: { columnWidth: '45%' } - } - } - }, - { - breakpoint: theme.breakpoints.values.lg, - options: { - plotOptions: { - bar: { columnWidth: '20%' } - } - } - }, - { - breakpoint: 600, - options: { - plotOptions: { - bar: { columnWidth: '15%' } - } - } - } - ] + if (isLoading) { + return } return ( - - - -
- - 124k - - - +12.6% - + +
+
+ + {title} + + + {formatShortCurrency(value)} + +
+ + +
diff --git a/src/views/dashboards/crm/EarningReportsWithTabs.tsx b/src/views/dashboards/crm/EarningReportsWithTabs.tsx index c03a2a9..4050c62 100644 --- a/src/views/dashboards/crm/EarningReportsWithTabs.tsx +++ b/src/views/dashboards/crm/EarningReportsWithTabs.tsx @@ -1,31 +1,33 @@ 'use client' // React Imports -import { useState } from 'react' import type { SyntheticEvent } from 'react' +import { useState } from 'react' // Next Imports import dynamic from 'next/dynamic' // MUI Imports -import Card from '@mui/material/Card' -import CardHeader from '@mui/material/CardHeader' -import CardContent from '@mui/material/CardContent' -import Tab from '@mui/material/Tab' +import TabContext from '@mui/lab/TabContext' import TabList from '@mui/lab/TabList' import TabPanel from '@mui/lab/TabPanel' -import TabContext from '@mui/lab/TabContext' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import CardHeader from '@mui/material/CardHeader' +import Tab from '@mui/material/Tab' import Typography from '@mui/material/Typography' import type { Theme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles' // Third Party Imports -import classnames from 'classnames' import type { ApexOptions } from 'apexcharts' +import classnames from 'classnames' // Components Imports -import OptionMenu from '@core/components/option-menu' import CustomAvatar from '@core/components/mui/Avatar' +import OptionMenu from '@core/components/option-menu' +import Loading from '../../../components/layout/shared/Loading' +import { formatShortCurrency } from '../../../utils/transform' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) @@ -33,39 +35,14 @@ const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexChart type ApexChartSeries = NonNullable type ApexChartSeriesData = Exclude -type TabCategory = 'orders' | 'sales' | 'profit' | 'income' - type TabType = { - type: TabCategory + type: string avatarIcon: string + date: any series: ApexChartSeries } -// Vars -const tabData: TabType[] = [ - { - type: 'orders', - avatarIcon: 'tabler-shopping-cart', - series: [{ data: [28, 10, 46, 38, 15, 30, 35, 28, 8] }] - }, - { - type: 'sales', - avatarIcon: 'tabler-chart-bar', - series: [{ data: [35, 25, 15, 40, 42, 25, 48, 8, 30] }] - }, - { - type: 'profit', - avatarIcon: 'tabler-currency-dollar', - series: [{ data: [10, 22, 27, 33, 42, 32, 27, 22, 8] }] - }, - { - type: 'income', - avatarIcon: 'tabler-chart-pie-2', - series: [{ data: [5, 9, 12, 18, 20, 25, 30, 36, 48] }] - } -] - -const renderTabs = (value: TabCategory) => { +const renderTabs = (tabData: TabType[], value: string) => { return tabData.map((item, index) => ( { )) } -const renderTabPanels = (value: TabCategory, theme: Theme, options: ApexOptions, colors: string[]) => { +const renderTabPanels = (tabData: TabType[], theme: Theme, options: ApexOptions, colors: string[]) => { return tabData.map((item, index) => { const max = Math.max(...((item.series[0] as ApexChartSeriesData).data as number[])) const seriesIndex = ((item.series[0] as ApexChartSeriesData).data as number[]).indexOf(max) @@ -101,7 +78,7 @@ const renderTabPanels = (value: TabCategory, theme: Theme, options: ApexOptions, { +const EarningReportsWithTabs = ({ data }: { data: TabType[] }) => { // States - const [value, setValue] = useState('orders') + const [value, setValue] = useState(data[0].type) // Hooks const theme = useTheme() @@ -121,7 +98,7 @@ const EarningReportsWithTabs = () => { // Vars const disabledText = 'var(--mui-palette-text-disabled)' - const handleChange = (event: SyntheticEvent, newValue: TabCategory) => { + const handleChange = (event: SyntheticEvent, newValue: string) => { setValue(newValue) } @@ -145,7 +122,7 @@ const EarningReportsWithTabs = () => { tooltip: { enabled: false }, dataLabels: { offsetY: -11, - formatter: val => `${val}k`, + formatter: val => formatShortCurrency(Number(val)), style: { fontWeight: 500, colors: ['var(--mui-palette-text-primary)'], @@ -173,7 +150,7 @@ const EarningReportsWithTabs = () => { xaxis: { axisTicks: { show: false }, axisBorder: { color: 'var(--mui-palette-divider)' }, - categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'], + categories: data.find(item => item.type === value)?.date, labels: { style: { colors: disabledText, @@ -185,7 +162,7 @@ const EarningReportsWithTabs = () => { yaxis: { labels: { offsetX: -18, - formatter: val => `$${val}k`, + formatter: val => `${formatShortCurrency(Number(val))}`, style: { colors: disabledText, fontFamily: theme.typography.fontFamily, @@ -235,31 +212,33 @@ const EarningReportsWithTabs = () => { /> - - {renderTabs(value)} - - - - -
- } - /> - - {renderTabPanels(value, theme, options, colors)} + {data.length > 1 && ( + + {renderTabs(data, value)} + + + + + + } + /> + + )} + {renderTabPanels(data, theme, options, colors)}
diff --git a/src/views/dashboards/crm/LastTransaction.tsx b/src/views/dashboards/crm/LastTransaction.tsx index a4a6362..aff7fb9 100644 --- a/src/views/dashboards/crm/LastTransaction.tsx +++ b/src/views/dashboards/crm/LastTransaction.tsx @@ -88,12 +88,12 @@ const statusObj: StatusObj = { verified: { text: 'Verified', color: 'success' } } -const LastTransaction = ({ serverMode }: { serverMode: SystemMode }) => { +const LastTransaction = () => { // Hooks const { mode } = useColorScheme() // Vars - const _mode = (mode === 'system' ? serverMode : mode) || serverMode + const _mode = mode as SystemMode return (