update
This commit is contained in:
@ -50,10 +50,13 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
"date-fns": "^3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"https": "^1.0.0",
|
"https": "^1.0.0",
|
||||||
|
"jspdf": "^3.0.1",
|
||||||
|
"jspdf-autotable": "^5.0.2",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"lucide-react": "^0.456.0",
|
"lucide-react": "^0.456.0",
|
||||||
"metronic-tailwind-react": "file:",
|
"metronic-tailwind-react": "file:",
|
||||||
@ -86,10 +89,12 @@
|
|||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vite-plugin-windicss": "^1.9.3",
|
"vite-plugin-windicss": "^1.9.3",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^1.4.0"
|
"yup": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/leaflet": "^1.9.14",
|
"@types/leaflet": "^1.9.14",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { doSaveLogActivity } from '@/actions/GlobalActions';
|
|||||||
import { CustomerProps } from '@/pages/master/provider/blocks/AddDialog';
|
import { CustomerProps } from '@/pages/master/provider/blocks/AddDialog';
|
||||||
import { Check, ChevronDown } from 'lucide-react';
|
import { Check, ChevronDown } from 'lucide-react';
|
||||||
import { initialStateNotification, selectedNotification, validateFormNotification } from './Types';
|
import { initialStateNotification, selectedNotification, validateFormNotification } from './Types';
|
||||||
|
import { useDebounce } from './useDebounce';
|
||||||
|
|
||||||
const API_URL_CUSTOMER = apiConfig.service_customer;
|
const API_URL_CUSTOMER = apiConfig.service_customer;
|
||||||
const API_URL_NOTIFICATION = apiConfig.service_notification;
|
const API_URL_NOTIFICATION = apiConfig.service_notification;
|
||||||
@ -39,6 +40,8 @@ const AddDialog = () => {
|
|||||||
const [alert, setAlert] = useState({ show: false, message: '' });
|
const [alert, setAlert] = useState({ show: false, message: '' });
|
||||||
const isSubmittingRef = useRef(false);
|
const isSubmittingRef = useRef(false);
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const debouncedSearchQuery = useDebounce(searchQuery, 500);
|
||||||
|
|
||||||
const [formField, setFormField] = useState<selectedNotification>(initialStateNotification);
|
const [formField, setFormField] = useState<selectedNotification>(initialStateNotification);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@ -95,7 +98,6 @@ const AddDialog = () => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('Something went wrong');
|
toast.error('Something went wrong');
|
||||||
console.log(err);
|
|
||||||
} finally {
|
} finally {
|
||||||
isSubmittingRef.current = false;
|
isSubmittingRef.current = false;
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
@ -104,22 +106,35 @@ const AddDialog = () => {
|
|||||||
[PostData, formField, handleAddDialog, reload]
|
[PostData, formField, handleAddDialog, reload]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getCustomerList = async (sorting: any) => {
|
useEffect(() => {
|
||||||
try {
|
const fetchCustomers = async () => {
|
||||||
sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting;
|
try {
|
||||||
const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
const filters = [];
|
||||||
limit: 100,
|
|
||||||
page: 1,
|
|
||||||
with_deleted: false,
|
|
||||||
order_field: sorting[0].id,
|
|
||||||
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC'
|
|
||||||
});
|
|
||||||
|
|
||||||
setCustomers(response?.data.list);
|
if (debouncedSearchQuery) {
|
||||||
} catch (error) {
|
filters.push({
|
||||||
console.error('Error fetching customer', error);
|
id: 'fullname',
|
||||||
}
|
value: debouncedSearchQuery
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, {
|
||||||
|
limit: 100,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'fullname',
|
||||||
|
order_direction: 'ASC',
|
||||||
|
filter: JSON.stringify(filters),
|
||||||
|
specialFilter: filters.length > 0
|
||||||
|
});
|
||||||
|
setCustomers(response?.data.list);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching customers', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCustomers();
|
||||||
|
}, [debouncedSearchQuery]);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -127,10 +142,6 @@ const AddDialog = () => {
|
|||||||
doCreateNotification(e);
|
doCreateNotification(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getCustomerList([{ id: 'id', desc: false }]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showAddDialog === false) {
|
if (showAddDialog === false) {
|
||||||
resetForm();
|
resetForm();
|
||||||
@ -217,7 +228,11 @@ const AddDialog = () => {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[400px] p-0">
|
<PopoverContent className="w-[400px] p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search Customer..." />
|
<CommandInput
|
||||||
|
placeholder="Search Customer..."
|
||||||
|
value={searchQuery}
|
||||||
|
onValueChange={setSearchQuery}
|
||||||
|
/>
|
||||||
<CommandList
|
<CommandList
|
||||||
className="max-h-[300px] overflow-y-auto"
|
className="max-h-[300px] overflow-y-auto"
|
||||||
style={{ touchAction: 'pan-y' }}
|
style={{ touchAction: 'pan-y' }}
|
||||||
@ -232,24 +247,20 @@ const AddDialog = () => {
|
|||||||
return (
|
return (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={customer.id}
|
key={customer.id}
|
||||||
value={customer.username}
|
value={customer.fullname}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
const customerId = customer.id;
|
setFormField((prev) => ({
|
||||||
setFormField((prev) => {
|
...prev,
|
||||||
const isSelected = prev.customers.includes(customerId);
|
customers: isSelected
|
||||||
return {
|
? prev.customers.filter((id) => id !== customer.id)
|
||||||
...prev,
|
: [...prev.customers, customer.id]
|
||||||
customers: isSelected
|
}));
|
||||||
? prev.customers.filter((id) => id !== customerId)
|
|
||||||
: [...prev.customers, customerId]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
className={isSelected ? 'bg-accent font-semibold' : ''}
|
className={isSelected ? 'bg-accent font-semibold' : ''}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
{isSelected && <Check className="w-4 h-4 text-green-600" />}
|
{isSelected && <Check className="w-4 h-4 text-green-600" />}
|
||||||
{customer.username}
|
{customer.fullname} ({customer.msisdn})
|
||||||
</span>
|
</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
);
|
);
|
||||||
|
|||||||
17
src/pages/notification/blocks/useDebounce.tsx
Normal file
17
src/pages/notification/blocks/useDebounce.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useState, useEffect } from 'react';
|
import React, { useCallback, useState, useEffect, useContext } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -11,6 +11,7 @@ import { DefaultTooltip, KeenIcon } from '@/components';
|
|||||||
import { toAbsoluteUrl } from '@/utils';
|
import { toAbsoluteUrl } from '@/utils';
|
||||||
import { useCallApi } from '@/hooks';
|
import { useCallApi } from '@/hooks';
|
||||||
import { apiConfig } from '@/config/api.config';
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { showDetailWalletContext } from './ShowDetailDialog';
|
||||||
|
|
||||||
interface TransferType {
|
interface TransferType {
|
||||||
id: string;
|
id: string;
|
||||||
@ -40,24 +41,44 @@ const FilterSection: React.FC<FilterSectionProps> = ({
|
|||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
const [category, setCategory] = useState<string[]>([]);
|
const [category, setCategory] = useState<string[]>([]);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const { doExportExcel, doExportPDF } = useContext(showDetailWalletContext);
|
||||||
|
|
||||||
|
const handleExportExcel = async () => {
|
||||||
|
try {
|
||||||
|
await doExportExcel();
|
||||||
|
// console.log('Excel export initiated successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during Excel export:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExportPDF = async () => {
|
||||||
|
try {
|
||||||
|
await doExportPDF();
|
||||||
|
// console.log('PDF export initiated successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during PDF export:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchTransferType = useCallback(async () => {
|
const fetchTransferType = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await GetData(`${apiConfig.service_transaction}/transactiontype/list`, {
|
const response = await GetData(`${apiConfig.service_transaction}/transactiontype/list`, {
|
||||||
limit: 100,
|
limit: 100,
|
||||||
page: 1,
|
page: 1,
|
||||||
with_deleted: false,
|
with_deleted: false,
|
||||||
order_field: 'created_at',
|
order_field: 'created_at',
|
||||||
order_direction: 'ASC'
|
order_direction: 'ASC'
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedData = (response?.data?.list || [])
|
const sortedData = (response?.data?.list || []).sort((a: any, b: any) =>
|
||||||
.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
a.name.localeCompare(b.name)
|
||||||
setTransferType(sortedData);
|
);
|
||||||
} catch (error) {
|
setTransferType(sortedData);
|
||||||
console.error('Error fetching transfer types', error);
|
} catch (error) {
|
||||||
}
|
console.error('Error fetching transfer types', error);
|
||||||
}, [GetData]);
|
}
|
||||||
|
}, [GetData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTransferType();
|
fetchTransferType();
|
||||||
@ -70,8 +91,8 @@ const FilterSection: React.FC<FilterSectionProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCategory(categories);
|
setCategory(categories);
|
||||||
}, [transaction, categoryMap]);
|
}, [transaction, categoryMap]);
|
||||||
|
|
||||||
const reverseCategoryMap = Object.fromEntries(
|
const reverseCategoryMap = Object.fromEntries(
|
||||||
Object.entries(categoryMap).map(([key, value]) => [value, key])
|
Object.entries(categoryMap).map(([key, value]) => [value, key])
|
||||||
@ -108,7 +129,7 @@ const FilterSection: React.FC<FilterSectionProps> = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTransferType) {
|
if (selectedTransferType) {
|
||||||
appliedFilters.id_transaction_type = selectedTransferType;
|
appliedFilters.id_transaction_type = selectedTransferType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,17 +221,19 @@ const FilterSection: React.FC<FilterSectionProps> = ({
|
|||||||
Filter Data
|
Filter Data
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<DefaultTooltip title={'Export Data'} placement={'top'}>
|
<div className="flex gap-2">
|
||||||
<Button variant={'outline'} className="h-7.5 min-w-[58px]">
|
<DefaultTooltip title={'Export to Excel'} placement={'top'}>
|
||||||
<img src={toAbsoluteUrl('/media/file-types/xls.svg')} className="" alt="" />
|
<Button variant={'outline'} className="h-7.5 min-w-[58px]" onClick={handleExportExcel}>
|
||||||
</Button>
|
<img src={toAbsoluteUrl('/media/file-types/xls.svg')} className="" alt="Excel" />
|
||||||
</DefaultTooltip>
|
</Button>
|
||||||
|
</DefaultTooltip>
|
||||||
|
|
||||||
<DefaultTooltip title={'Export Data'} placement={'top'}>
|
<DefaultTooltip title={'Export to PDF'} placement={'top'}>
|
||||||
<Button variant={'outline'} className="h-7.5 min-w-[58px]">
|
<Button variant={'outline'} className="h-7.5 min-w-[58px]" onClick={handleExportPDF}>
|
||||||
<img src={toAbsoluteUrl('/media/file-types/pdf.svg')} className="" alt="" />
|
<img src={toAbsoluteUrl('/media/file-types/pdf.svg')} className="" alt="PDF" />
|
||||||
</Button>
|
</Button>
|
||||||
</DefaultTooltip>
|
</DefaultTooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,13 +14,20 @@ import { useManageStatementContext } from '../hooks/useManageWalletStatementCont
|
|||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import FilterSection from './FilterSection';
|
import FilterSection from './FilterSection';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import jsPDF from 'jspdf';
|
||||||
|
import 'jspdf-autotable';
|
||||||
|
|
||||||
interface ContextProps {
|
interface ContextProps {
|
||||||
doExportData: (sorting: any, filter: any) => Promise<any>;
|
doExportData: (sorting: any, filter: any) => Promise<any>;
|
||||||
|
doExportExcel: () => Promise<void>;
|
||||||
|
doExportPDF: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialProps: ContextProps = {
|
const initialProps: ContextProps = {
|
||||||
doExportData: async () => ({ data: [], totalCount: 0 })
|
doExportData: async () => ({ data: [], totalCount: 0 }),
|
||||||
|
doExportExcel: async () => {},
|
||||||
|
doExportPDF: async () => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showDetailWalletContext = createContext<ContextProps>(initialProps);
|
export const showDetailWalletContext = createContext<ContextProps>(initialProps);
|
||||||
@ -174,6 +181,297 @@ const ShowDetailWalletDialog = () => {
|
|||||||
[GetData, filters, selectedWallet]
|
[GetData, filters, selectedWallet]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getAllTransactionData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await GetData(
|
||||||
|
`${apiConfig.service_wallet}/dashboard/balance/list-balance-detail/${selectedWallet?.ID}`,
|
||||||
|
{
|
||||||
|
limit: 10000,
|
||||||
|
page: 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: 'amount',
|
||||||
|
order_direction: 'DESC',
|
||||||
|
filter: JSON.stringify(filters)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response?.data?.list || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching all transaction data', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTransactionData = (data: any[]) => {
|
||||||
|
return data.map((item) => ({
|
||||||
|
'Transaction Code': item.transaction_code || '-',
|
||||||
|
'MSISDN Reffer': item.msisdn_reff || '-',
|
||||||
|
'Transaction Type': item.transaction_type?.name || '-',
|
||||||
|
Type: item.type || '-',
|
||||||
|
'Pre Amount': item.pre_amount?.toFixed(2) || '0.00',
|
||||||
|
Amount: item.amount?.toFixed(2) || '0.00',
|
||||||
|
'Post Amount': item.post_amount?.toFixed(2) || '0.00',
|
||||||
|
Category: categoryMap[item.category] || '-',
|
||||||
|
Date: item.date
|
||||||
|
? new Date(item.date).toLocaleString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
: '-',
|
||||||
|
Purpose: item.purpose || '-',
|
||||||
|
Notes: item.notes || '-'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const doExportExcel = async () => {
|
||||||
|
try {
|
||||||
|
const allData = await getAllTransactionData();
|
||||||
|
const formattedData = formatTransactionData(allData);
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet([]);
|
||||||
|
|
||||||
|
const companyHeader = [
|
||||||
|
['T•PAY'],
|
||||||
|
['Telin Digital Solution'],
|
||||||
|
['Av. Pres. Nicolau Lobato, Dili'],
|
||||||
|
['Tel: (+670) 74147147 / 147'],
|
||||||
|
['www.t-pay.tl'],
|
||||||
|
[''],
|
||||||
|
['Wallet Statement Details'],
|
||||||
|
[`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`],
|
||||||
|
[`Wallet ID: ${selectedWallet?.ID || '-'}`],
|
||||||
|
['']
|
||||||
|
];
|
||||||
|
|
||||||
|
const columnHeaders = [
|
||||||
|
[
|
||||||
|
'Transaction Code',
|
||||||
|
'MSISDN Reffer',
|
||||||
|
'Transaction Type',
|
||||||
|
'Type',
|
||||||
|
'Pre Amount',
|
||||||
|
'Amount',
|
||||||
|
'Post Amount',
|
||||||
|
'Category',
|
||||||
|
'Date',
|
||||||
|
'Purpose',
|
||||||
|
'Notes'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const excelData = [
|
||||||
|
...companyHeader,
|
||||||
|
...columnHeaders,
|
||||||
|
...formattedData.map((item) => Object.values(item))
|
||||||
|
];
|
||||||
|
|
||||||
|
XLSX.utils.sheet_add_aoa(ws, excelData);
|
||||||
|
|
||||||
|
const colWidths = [
|
||||||
|
{ wch: 20 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 25 },
|
||||||
|
{ wch: 10 },
|
||||||
|
{ wch: 12 },
|
||||||
|
{ wch: 12 },
|
||||||
|
{ wch: 12 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 20 },
|
||||||
|
{ wch: 25 },
|
||||||
|
{ wch: 25 }
|
||||||
|
];
|
||||||
|
ws['!cols'] = colWidths;
|
||||||
|
|
||||||
|
const tpayCell = XLSX.utils.encode_cell({ r: 0, c: 0 });
|
||||||
|
if (ws[tpayCell]) {
|
||||||
|
ws[tpayCell].s = {
|
||||||
|
font: { bold: true, sz: 18, color: { rgb: '000000' } },
|
||||||
|
alignment: { horizontal: 'center' },
|
||||||
|
fill: { fgColor: { rgb: 'F4C2C2' } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < 5; i++) {
|
||||||
|
const cellRef = XLSX.utils.encode_cell({ r: i, c: 0 });
|
||||||
|
if (ws[cellRef]) {
|
||||||
|
ws[cellRef].s = {
|
||||||
|
font: { sz: 12, color: { rgb: '000000' } },
|
||||||
|
alignment: { horizontal: 'center' },
|
||||||
|
fill: { fgColor: { rgb: 'F4C2C2' } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleCell = XLSX.utils.encode_cell({ r: 6, c: 0 });
|
||||||
|
if (ws[titleCell]) {
|
||||||
|
ws[titleCell].s = {
|
||||||
|
font: { bold: true, sz: 14 },
|
||||||
|
alignment: { horizontal: 'center' }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 7; i < 9; i++) {
|
||||||
|
const cellRef = XLSX.utils.encode_cell({ r: i, c: 0 });
|
||||||
|
if (ws[cellRef]) {
|
||||||
|
ws[cellRef].s = {
|
||||||
|
font: { sz: 11 },
|
||||||
|
alignment: { horizontal: 'left' }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const range = XLSX.utils.encode_range({ s: { r: i, c: 0 }, e: { r: i, c: 10 } });
|
||||||
|
if (!ws['!merges']) ws['!merges'] = [];
|
||||||
|
ws['!merges'].push(XLSX.utils.decode_range(range));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let col = 0; col < columnHeaders[0].length; col++) {
|
||||||
|
const cellRef = XLSX.utils.encode_cell({ r: 10, c: col });
|
||||||
|
if (ws[cellRef]) {
|
||||||
|
ws[cellRef].s = {
|
||||||
|
font: { bold: true, color: { rgb: 'FFFFFF' } },
|
||||||
|
fill: { fgColor: { rgb: '4472C4' } },
|
||||||
|
alignment: { horizontal: 'center' },
|
||||||
|
border: {
|
||||||
|
top: { style: 'thin', color: { rgb: '000000' } },
|
||||||
|
bottom: { style: 'thin', color: { rgb: '000000' } },
|
||||||
|
left: { style: 'thin', color: { rgb: '000000' } },
|
||||||
|
right: { style: 'thin', color: { rgb: '000000' } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let row = 11; row < 11 + formattedData.length; row++) {
|
||||||
|
for (let col = 0; col < columnHeaders[0].length; col++) {
|
||||||
|
const cellRef = XLSX.utils.encode_cell({ r: row, c: col });
|
||||||
|
if (ws[cellRef]) {
|
||||||
|
ws[cellRef].s = {
|
||||||
|
border: {
|
||||||
|
top: { style: 'thin', color: { rgb: 'D9D9D9' } },
|
||||||
|
bottom: { style: 'thin', color: { rgb: 'D9D9D9' } },
|
||||||
|
left: { style: 'thin', color: { rgb: 'D9D9D9' } },
|
||||||
|
right: { style: 'thin', color: { rgb: 'D9D9D9' } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ([4, 5, 6].includes(col)) {
|
||||||
|
ws[cellRef].s.alignment = { horizontal: 'right' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, 'Wallet Statement');
|
||||||
|
|
||||||
|
const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`;
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
|
||||||
|
// console.log('Excel export completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting to Excel:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const doExportPDF = async () => {
|
||||||
|
try {
|
||||||
|
const allData = await getAllTransactionData();
|
||||||
|
const formattedData = formatTransactionData(allData);
|
||||||
|
|
||||||
|
const pdf = new jsPDF('l', 'mm', 'a4');
|
||||||
|
|
||||||
|
pdf.setFontSize(20);
|
||||||
|
pdf.setFont('helvetica', 'bold');
|
||||||
|
pdf.text('T•PAY', 148, 20, { align: 'center' });
|
||||||
|
|
||||||
|
pdf.setFontSize(12);
|
||||||
|
pdf.setFont('helvetica', 'normal');
|
||||||
|
pdf.text('Telin Digital Solution', 148, 30, { align: 'center' });
|
||||||
|
pdf.text('Av. Pres. Nicolau Lobato, Dili', 148, 36, { align: 'center' });
|
||||||
|
pdf.text('Tel : (+670) 7414747 / 147', 148, 42, { align: 'center' });
|
||||||
|
pdf.text('www.t-pay.tl', 148, 48, { align: 'center' });
|
||||||
|
|
||||||
|
pdf.setFontSize(14);
|
||||||
|
pdf.setFont('helvetica', 'bold');
|
||||||
|
pdf.text('Wallet Statement Details', 148, 60, { align: 'center' });
|
||||||
|
|
||||||
|
pdf.setFontSize(10);
|
||||||
|
pdf.setFont('helvetica', 'normal');
|
||||||
|
pdf.text(`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`, 20, 70);
|
||||||
|
pdf.text(`Wallet ID: ${selectedWallet?.ID || '-'}`, 20, 76);
|
||||||
|
|
||||||
|
const tableData = formattedData.map((item) => [
|
||||||
|
item['Transaction Code'],
|
||||||
|
item['MSISDN Reffer'],
|
||||||
|
item['Transaction Type'],
|
||||||
|
item['Type'],
|
||||||
|
item['Pre Amount'],
|
||||||
|
item['Amount'],
|
||||||
|
item['Post Amount'],
|
||||||
|
item['Category'],
|
||||||
|
item['Date'],
|
||||||
|
item['Purpose'],
|
||||||
|
item['Notes']
|
||||||
|
]);
|
||||||
|
|
||||||
|
(pdf as any).autoTable({
|
||||||
|
head: [
|
||||||
|
[
|
||||||
|
'Transaction Code',
|
||||||
|
'MSISDN Reffer',
|
||||||
|
'Transaction Type',
|
||||||
|
'Type',
|
||||||
|
'Pre Amount',
|
||||||
|
'Amount',
|
||||||
|
'Post Amount',
|
||||||
|
'Category',
|
||||||
|
'Date',
|
||||||
|
'Purpose',
|
||||||
|
'Notes'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
body: tableData,
|
||||||
|
startY: 85,
|
||||||
|
styles: {
|
||||||
|
fontSize: 8,
|
||||||
|
cellPadding: 2
|
||||||
|
},
|
||||||
|
headStyles: {
|
||||||
|
fillColor: [66, 66, 66],
|
||||||
|
textColor: [255, 255, 255],
|
||||||
|
fontStyle: 'bold'
|
||||||
|
},
|
||||||
|
columnStyles: {
|
||||||
|
0: { cellWidth: 20 },
|
||||||
|
1: { cellWidth: 20 },
|
||||||
|
2: { cellWidth: 25 },
|
||||||
|
3: { cellWidth: 15 },
|
||||||
|
4: { cellWidth: 18 },
|
||||||
|
5: { cellWidth: 18 },
|
||||||
|
6: { cellWidth: 18 },
|
||||||
|
7: { cellWidth: 25 },
|
||||||
|
8: { cellWidth: 25 },
|
||||||
|
9: { cellWidth: 30 },
|
||||||
|
10: { cellWidth: 30 }
|
||||||
|
},
|
||||||
|
margin: { top: 85, left: 10, right: 10 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.pdf`;
|
||||||
|
pdf.save(fileName);
|
||||||
|
|
||||||
|
// console.log('PDF export completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting to PDF:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const doExportData = async (sorting: any, filter: any) => {
|
const doExportData = async (sorting: any, filter: any) => {
|
||||||
const user = localStorage.getItem(
|
const user = localStorage.getItem(
|
||||||
`${import.meta.env.VITE_APP_NAME}-auth-v${import.meta.env.VITE_APP_VERSION}`
|
`${import.meta.env.VITE_APP_NAME}-auth-v${import.meta.env.VITE_APP_VERSION}`
|
||||||
@ -219,7 +517,7 @@ const ShowDetailWalletDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<showDetailWalletContext.Provider value={{ doExportData }}>
|
<showDetailWalletContext.Provider value={{ doExportData, doExportExcel, doExportPDF }}>
|
||||||
<Toaster expand visibleToasts={9} duration={3000} />
|
<Toaster expand visibleToasts={9} duration={3000} />
|
||||||
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
|
||||||
<DialogContent className="w-screen max-w-screen max-h-screen p-4 overflow-hidden">
|
<DialogContent className="w-screen max-w-screen max-h-screen p-4 overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user