diff --git a/.env.development b/.env.development index 5845f2e..01556f5 100644 --- a/.env.development +++ b/.env.development @@ -3,8 +3,9 @@ VITE_APP_VERSION=1=9.1.1 GENERATE_SOURCEMAP=false # VITE_APP_API_URL= -VITE_APP_API_URL=http://127.0.0.1:4003/apitest +# VITE_APP_API_URL=http://127.0.0.1:4003/apitest VITE_APP_API_URL=https://tpay.shiblysolution.id/api +# VITE_APP_API_URL=http://api-v2.tpay.tl/api # VITE_APP_URL_TRANSACTION=https://tpay.shiblysolution.id/test/x/api # VITE_APP_API_URL=http://127.0.0.1:4003/apitesting VITE_ENV=development diff --git a/.env.production b/.env.production index 52981df..e17f1e0 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,7 @@ VITE_APP_NAME=tpay-dashboard-tl VITE_APP_VERSION=1=9.1.1 GENERATE_SOURCEMAP=false - -VITE_APP_API_URL= -VITE_BASE_URL= +VITE_APP_API_URL=https://api-v2.tpay.tl/api +# VITE_APP_API_URL= +VITE_BASE_URL=/ VITE_ENV=production diff --git a/.env.staging b/.env.staging index 5fb90d4..e02ae19 100644 --- a/.env.staging +++ b/.env.staging @@ -3,5 +3,6 @@ VITE_APP_VERSION=1=9.1.1 GENERATE_SOURCEMAP=false VITE_APP_API_URL=https://tpay.shiblysolution.id/api +# VITE_APP_API_URL=http://api-v2.tpay.tl/api VITE_BASE_URL=/dashboard/ VITE_ENV=staging \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml index be0280b..9b44684 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,46 +1,134 @@ +when: + commit: + message: + exclude: + - '\[skip ci\]' + - '\[ci skip\]' + - '\[no ci\]' + steps: - - name: setup - image: node:22-alpine - commands: - - apk update && apk add git - - npm i typescript -g - - npm install - - echo '✅ success setup project' - when: - branch: - - main + - name: telegram start notify + image: appleboy/drone-telegram + settings: + token: 7616010754:AAGXMf8x-roKozdT0j-bOZ1y9dmbulbKPbM + to: -4765409490 + message: | + 🚀 *Woodpecker CI Build Starting* 🚀 - - name: build-swagger - image: node:22-alpine - environment: - NODE_ENV: prod - commands: - - npm run swagger - when: - branch: - - main + *Repo:* ${CI_REPO_OWNER}/${CI_REPO_NAME} + *Repo Link:* ${CI_REPO_URL} + *Branch:* ${CI_COMMIT_BRANCH} - - name: build-src - image: node:22-alpine - environment: - NODE_ENV: prod - commands: - - npm i typescript -g - - npm run build:production - - echo '✅ success compile typescript and ready to deploy' - when: - branch: - - main + *Build Status:* *starting* + *Build Link:* ${CI_PIPELINE_URL} - - name: deploy - image: docker - commands: - - docker builder prune -f - - docker container prune -f - - docker-compose build - - docker-compose up -d --no-deps --build --remove-orphans - volumes: - - /var/run/docker.sock:/var/run/docker.sock - when: - branch: - - main + *Commit ID:* `${CI_COMMIT_SHA:0:8}` + *Commit By:* {{commit.author}} + *Commit Link:* {{commit.link}} + *Commit Msg:* {{commit.message}} + 🙏 hopefully success {{commit.author}} + when: + branch: + - master + + - name: setup + image: node:22-alpine + commands: + - apk update && apk add git + - npm i typescript -g + - npm install + - echo '✅ success setup project' + when: + branch: + - master + + - name: build-src + image: node:22-alpine + environment: + NODE_ENV: prod + commands: + - npm i typescript -g + - npm run build:production + - echo '✅ success compile typescript' + when: + branch: + - master + depends_on: [setup] + + - name: build-image + image: docker + commands: + - docker builder prune -f + - docker container prune -f + - docker-compose build + volumes: + - /var/run/docker.sock:/var/run/docker.sock + when: + branch: + - master + depends_on: [setup, build-src] + + - name: deploy + image: docker + commands: + - docker-compose up -d --no-deps --remove-orphans + volumes: + - /var/run/docker.sock:/var/run/docker.sock + when: + branch: + - master + depends_on: [setup, build-src, build-image] + + - name: telegram failed notify + image: appleboy/drone-telegram + settings: + token: 7616010754:AAGXMf8x-roKozdT0j-bOZ1y9dmbulbKPbM + to: -4765409490 + message: | + 🚀 *Woodpecker CI Build Finish* 🚀 + + *Repo:* ${CI_REPO_OWNER}/${CI_REPO_NAME} + *Repo Link:* ${CI_REPO_URL} + *Branch:* ${CI_COMMIT_BRANCH} + + *Build Status:* *Failed* + *Build Link:* ${CI_PIPELINE_URL} + + *Commit ID:* `${CI_COMMIT_SHA:0:8}` + *Commit By:* {{commit.author}} + *Commit Link:* {{commit.link}} + *Commit Msg:* {{commit.message}} + 🙈 build failed. Fix me please, before client is aware. + when: + status: + - failure + - cancel + branch: + - master + + - name: telegram success notify + image: appleboy/drone-telegram + settings: + token: 7616010754:AAGXMf8x-roKozdT0j-bOZ1y9dmbulbKPbM + to: -4765409490 + message: | + 🚀 *Woodpecker CI Build Finish* 🚀 + + *Repo:* ${CI_REPO_OWNER}/${CI_REPO_NAME} + *Repo Link:* ${CI_REPO_URL} + *Branch:* ${CI_COMMIT_BRANCH} + + *Build Status:* *Success* + *Build Link:* ${CI_PIPELINE_URL} + + *Commit ID:* `${CI_COMMIT_SHA:0:8}` + *Commit By:* {{commit.author}} + *Commit Link:* {{commit.link}} + *Commit Msg:* {{commit.message}} + 😎 build succeeded. Good job {{commit.author}} + when: + status: + - success + branch: + - master + depends_on: [setup, build-src, build-image, deploy] diff --git a/Dockerfile b/Dockerfile index 8fbc672..a3d6f4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,20 +20,22 @@ COPY eslint.config.mjs ./eslint.config.mjs COPY .env.production ./.env.production COPY .prettierrc ./.prettierrc COPY index.html ./index.html +COPY vhosts.conf ./vhosts.conf RUN npm run build:production -FROM node:22-alpine +# FROM node:22-alpine # Copy node modules and build directory from base image to new image -COPY --from=base ./node_modules ./node_modules -COPY --from=base ./package.json ./package.json -COPY --from=base /dist /dist -COPY --from=base /src /src -COPY --from=base /public /public +# COPY --from=base ./node_modules ./node_modules +# COPY --from=base ./package.json ./package.json +# COPY --from=base /dist /dist +# COPY --from=base /src /src +# COPY --from=base /public /public # Expose required port FROM nginx:alpine COPY --from=base /dist /usr/share/nginx/html +COPY --from=base /vhosts.conf /etc/nginx/conf.d/default.conf CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/package.json b/package.json index 43c45d3..bc73844 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "date-fns": "^3.0.0", "formik": "^2.4.6", "helmet": "^8.1.0", + "html2canvas": "^1.4.1", "https": "^1.0.0", "leaflet": "^1.9.4", "lucide-react": "^0.456.0", diff --git a/public/media/images/card.png b/public/media/images/card.png new file mode 100644 index 0000000..2ec5af6 Binary files /dev/null and b/public/media/images/card.png differ diff --git a/public/media/images/fixed-card.png b/public/media/images/fixed-card.png new file mode 100644 index 0000000..93f612a Binary files /dev/null and b/public/media/images/fixed-card.png differ diff --git a/src/pages/account/home/user-profile/AccountUserProfileContent.tsx b/src/pages/account/home/user-profile/AccountUserProfileContent.tsx index 76cc820..b971b5e 100644 --- a/src/pages/account/home/user-profile/AccountUserProfileContent.tsx +++ b/src/pages/account/home/user-profile/AccountUserProfileContent.tsx @@ -6,7 +6,7 @@ const AccountUserProfileContent = () => { return (
- + {/* */} {/* Uncomment the line below to enable the Delete Account feature */} diff --git a/src/pages/account/home/user-profile/blocks/BasicSettings.tsx b/src/pages/account/home/user-profile/blocks/BasicSettings.tsx index 0d3847b..0ac6b1a 100644 --- a/src/pages/account/home/user-profile/blocks/BasicSettings.tsx +++ b/src/pages/account/home/user-profile/blocks/BasicSettings.tsx @@ -63,7 +63,8 @@ const BasicSettings = () => { setNewName(e.target.value)} disabled={isSubmitting} /> @@ -73,7 +74,8 @@ const BasicSettings = () => { setNewUsername(e.target.value)} disabled={isSubmitting} /> @@ -84,7 +86,8 @@ const BasicSettings = () => { setNewEmail(e.target.value)} disabled={isSubmitting} /> diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index fbfa443..9ee6e5d 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -269,7 +269,7 @@ const DashboardHomePage = () => {
{bankaccount?.data && bankaccount?.data.length > 0 - // && getAuth()?.statusbalance=='Y' + && getAuth()?.statusbalance=='Y' ? ( bankaccount?.data.map((bankaccountdatas: { id_balance:string,amount: string, credit_limit: string, monthly_limit: string; wallet: string; }, index: number) => ( = ({ meta: { headerClassName: 'min-w-[80px]' }, }, { - accessorKey: 'amount', - header: ({ column }: any) => , + accessorKey: 'pre_amount', + header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue()?.toFixed(2), meta: { headerClassName: 'min-w-[100px]' }, }, { - accessorKey: 'pre_amount', - header: ({ column }: any) => , + accessorKey: 'amount', + header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue()?.toFixed(2), meta: { headerClassName: 'min-w-[100px]' }, diff --git a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx index 861dcea..7766169 100644 --- a/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx +++ b/src/pages/disbursement/history-transaction/hooks/TransactionContext.tsx @@ -54,7 +54,7 @@ const initialProps: ContextProps = { detailLogData: null }; -type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y'; +type StatusCode = 'W' | 'P' | 'F' | 'D' | 'Y' | 'O'; interface StatusInfo { label: string; @@ -64,10 +64,11 @@ interface StatusInfo { const statusMap: Record = { W: { label: 'Waiting', bg: 'bg-yellow-100', text: 'text-yellow-600' }, - P: { label: 'Pending', bg: 'bg-blue-100', text: 'text-blue-600' }, + P: { label: 'Pending', bg: 'bg-yellow-100', text: 'text-blue-600' }, F: { label: 'Fail', bg: 'bg-red-100', text: 'text-red-600' }, D: { label: 'Done', bg: 'bg-green-100', text: 'text-green-600' }, - Y: { label: 'Active', bg: 'bg-green-100', text: 'text-green-600' } + Y: { label: 'Active', bg: 'bg-green-100', text: 'text-green-600' }, + O: { label: 'On Process', bg: 'bg-blue-100', text: 'text-blue-600' } }; const ManageTransactionContext = createContext(initialProps); @@ -172,7 +173,7 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { ); }, meta: { - headerClassName: 'w-[100px]', + headerClassName: 'w-[200px] text-center', cellClassName: 'text-center' } }, diff --git a/src/pages/members/agent-balance/AgentBalance.tsx b/src/pages/members/agent-balance/AgentBalance.tsx index 1c03506..80db9c5 100644 --- a/src/pages/members/agent-balance/AgentBalance.tsx +++ b/src/pages/members/agent-balance/AgentBalance.tsx @@ -2,6 +2,7 @@ import { DataTable } from '@/components/ui/DataTable'; import { getColumns } from './Column'; import { apiConfig } from '@/config/api.config'; import axios, { AxiosResponse } from 'axios'; +import html2canvas from 'html2canvas'; import { Helmet } from 'react-helmet'; import { DialogContent, @@ -18,15 +19,11 @@ import { Breadcrumbs, Link } from '@mui/material'; -import { useState, useEffect } from 'react'; -import CloseIcon from '@mui/icons-material/Close'; -import Divider from '@mui/material/Divider'; -import ConfirmDialog from '@/components/confirm'; +import { useState, useEffect, useRef } from 'react'; import { toast } from 'sonner'; import { Container, DataGridLoader, DataGridProvider, LoaderTransparant } from '@/components'; import { ListToolBar } from './ListToolbar'; -import { RefreshCw } from 'lucide-react'; -import { setgroups } from 'process'; +import { toAbsoluteUrl } from '@/utils'; const BASE_URL = apiConfig.service_customer; let initBalance = { @@ -47,40 +44,43 @@ const AgentBalance = () => { const [dataBalance, setDataBalance] = useState([]); const [formData, setFormData] = useState(initBalance); const [pageIndex, setPageIndex] = useState(1); + const [currentPage, setCurrentPage] = useState(0); const [pageSize, setPageSize] = useState(10); const [dialogType, setDialogType] = useState(''); const [dialogOpen, setDialogOpen] = useState(false); const [isReloading, setIsReloading] = useState(false); + const [barcode, setBarcode] = useState(''); + // const [selectedMember, setSelectedMember] = useState(initBalance); useEffect(() => { - fetchAgentBalance(); + // fetchAgentBalance(); }, []); - async function fetchAgentBalance(): Promise { - setIsReloading(true); - try { - let balances = await axios.get(`${BASE_URL}/customer/agent-balance`, { - params: { - limit: 20, - page: 1, - with_deleted: false, - order_field: 'created_at', - order_direction: 'DESC' - } - }); - let temp = 1; - let resBalance = balances.data.data.list.map((el: any) => { - el.no = temp++; - if (!el.agent_name) el.agent_name = '' - return el; - }); - setDataBalance(resBalance); - } catch (error: any) { - alert(error.message); - console.log(error); - } finally { - setIsReloading(false); - } + const fetchAgentBalance = async ( + page: number, + limit: number, + sorting: any, + filter: any + ) => { + filter = filter.length ? filter : {}; + let balances = await axios.get(`${BASE_URL}/customer/agent-balance`, { + params: { + limit: limit, + page: page + 1, // +1 because your API uses 1-based pages + with_deleted: false, + order_field: 'created_at', + order_direction: 'DESC', + filter: JSON.stringify(filter), + specialFilter: true + } + }); + let temp = 1; + let resBalance = balances.data.data.list.map((el: any) => { + el.no = temp++; + if (!el.agent_name) el.agent_name = '' + return el; + }); + return { data: resBalance, totalCount: balances?.data.data.total_count } } const openDialog = () => setIsDialogOpen(true); @@ -94,16 +94,34 @@ const AgentBalance = () => { openDialog(); } - const handleUpdate = (group: any) => { + const handleUpdate = async (data: any) => { + await getQrCode(data.id); + setFormData(data); setDialogType('update'); setIsDialogOpen(true); }; - const handleReload = () => { + const handleReload = async () => { setIsReloading(true); - fetchAgentBalance(); + await fetchAgentBalance(currentPage, pageSize, '',''); + setIsReloading(false); + // fetchAgentBalance(); }; + async function getQrCode(customerId: any): Promise { + try { + setBarcode(``); + let getToken = await axios.get(`${BASE_URL}/token/get`, { params: { id: customerId } }); + console.log(getToken.data.data); + setBarcode(getToken.data.data.qrCode); + } catch (error: any) { + toast.warning(error.message); + console.log(error); + } finally { + // setBarcode(`https://miro.medium.com/v2/resize:fit:250/1*DcceOyLkzU--EzP4PYKd_g.png`) + } + } + if (loading) return ; return ( @@ -139,99 +157,162 @@ const AgentBalance = () => { {isReloading && ( )} - - } - layout={{ card: true }} - serverSide={false} - onRowSelectionChange={(selected, table: any) => { - const selectedRow = table.getSelectedRowModel().rows[0]; - if (selectedRow) handleUpdate(selectedRow.original); - }} - /> + {!isReloading && ( + fetchAgentBalance(pageIndex, pageSize, sorting, columnFilters) + } + toolbar={ + + } + onRowSelectionChange={(selected, table: any) => { + const selectedRow = table.getSelectedRowModel().rows[0]; + if (selectedRow) handleUpdate(selectedRow.original); + }} + /> + )}
{/*
*/} - {/* - -
- - {dialogType === 'create' ? 'Create New Group' : 'Update Group'} - - - - -
- -
-
-
- - -
-
- - - - } - label="Yes" - /> - } - label="No" - /> - - -
-
- - -
- -
+ + +
+
-
*/} +
); }; +// ==================== +const PaymentCard = ({ barcode, agent_name, customerId }: any) => { + const [token_legacy, setToken_legacy] = useState(''); + const cardRef: any = useRef(null); + + const waitForImageLoad = (img: HTMLImageElement): Promise => { + return new Promise((resolve) => { + if (img.complete && img.naturalHeight !== 0) { + resolve(); + } else { + toast.warning(`No QR Code`); + img.onload = () => resolve(); + } + }); + }; + + const handleDownload = async () => { + if (!cardRef.current) return; + + const img = cardRef.current.querySelector('img'); + if (img) { + await waitForImageLoad(img); + } else { + return toast.warning(`No QR Code`); + } + + const canvas = await html2canvas(cardRef.current, { + useCORS: true, + allowTaint: false, + backgroundColor: 'white', + scale: 2 + }); + + const dataUrl = canvas.toDataURL('image/png'); + const link = document.createElement('a'); + link.href = dataUrl; + link.download = `${agent_name}_barcode_card.png`; + link.click(); + }; + + const updateTokenLegacy = async () => { + try { + if (!token_legacy) toast.warning(`Token Legacy can not be empty`); + await axios.post(`${BASE_URL}/token/insert-token-legacy`, { + customerid: customerId, + token_legacy + }); + toast.success(`Success Update Token Legacy`); + } catch (error: any) { + toast.error(error.message); + } + }; + + return ( +
+
+ Card Frame + +
+

{agent_name}

+ + {barcode && ( +
+ Barcode +
+ )} + +
+ +
+
+

Imprime husi : Telin Digital Solution

+

Imprime : 01–06–2025

+
+
+
+
+ +
+
+ +