From 5f75b8658af8f450ae244e2a9cc215a6d01fdd06 Mon Sep 17 00:00:00 2001 From: Vassshhh Date: Sat, 6 Sep 2025 13:37:37 +0700 Subject: [PATCH] ok --- src/components/DailyCharts.js | 45 +++++----- src/helpers/transactionHelpers.js | 7 +- src/pages/CafePage.js | 76 +++++++++++++++++ src/pages/Cart.js | 106 +++++++++++++++++++---- src/pages/PrintPage.js | 137 +++++++++++++----------------- src/pages/Transactions.js | 112 ++++++++++++++++-------- src/print.css | 115 +++++++------------------ 7 files changed, 361 insertions(+), 237 deletions(-) diff --git a/src/components/DailyCharts.js b/src/components/DailyCharts.js index 74f3210..9c19ff6 100644 --- a/src/components/DailyCharts.js +++ b/src/components/DailyCharts.js @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import dayjs from "dayjs"; import Chart from "react-apexcharts"; import styles from "./BarChart.module.css"; // Import the CSS module @@ -85,7 +86,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ } let totalValue = seriesData.reduce((acc, val) => acc + val, 0); return { - date: new Date(dayData.date).toLocaleDateString(), + date: dayData.date, categories, series: [ { @@ -113,15 +114,11 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ } const formatDate = (dateString) => { - const date = new Date(dateString); - const monthNames = [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - ]; - const month = monthNames[date.getMonth()]; - const day = date.getDate(); - return { month, day }; + const d = dayjs(dateString, ["YYYY-MM-DD", "YYYY-MM-DDTHH:mm:ssZ"]); + return { month: d.format("MMM"), day: d.format("D") }; }; + return (
@@ -142,27 +139,37 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ key={indexx} className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive }`} - style={{ position: 'relative' }} + style={{ position: 'relative', width: 'calc(100% / 7)' }} onClick={() => type == 'yesterday' && selectedIndex == -1 || type != 'yesterday' && selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1) } > -
+
{indexx !== chartData.length - 1 ? ( - <> - {day}{" "} - {(indexx === 0 || (formatDate(chartData[indexx - 1].date).month !== month && type != 'weekly')) && month} - +

{day}{" "} + {( + indexx === 0 || + (indexx > 0 && + dayjs(chartData[indexx - 1].date).month() !== dayjs(item.date).month() && + type !== "weekly") + ) && month} +

) : ( - <> - {type != 'weekly' ? 'Hari ini' : day} - +

+ {type != 'weekly' ? 'Hari ini' : day + ' ' + month} +

)}
- {index == indexx &&

+ {index == indexx &&

{graphFilter === 'transactions' ? chartData[indexx].totalValue : formatRupiah(chartData[indexx].totalValue)} @@ -193,7 +200,7 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ } else { return formatRupiah(val); // format Rupiah } - + }, style: { colors: [(index == chartData.length - 1 || selectedIndex != -1) ? "#000" : "transparent"], diff --git a/src/helpers/transactionHelpers.js b/src/helpers/transactionHelpers.js index adec83c..21cdfd5 100644 --- a/src/helpers/transactionHelpers.js +++ b/src/helpers/transactionHelpers.js @@ -252,10 +252,9 @@ export const handlePaymentFromClerk = async ( ); if (response.ok) { - // Handle success response - console.log("Transaction successful!"); - // Optionally return response data or handle further actions upon success - return true; + const data = await response.json(); + console.log("Transaction successful!", data); + return data; } else { // Handle error response console.error("Transaction failed:", response.statusText); diff --git a/src/pages/CafePage.js b/src/pages/CafePage.js index e184609..0484cce 100644 --- a/src/pages/CafePage.js +++ b/src/pages/CafePage.js @@ -103,6 +103,17 @@ function CafePage({ // }; const [isTablet, setIsTablet] = useState(window.innerWidth >= 768); + const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement); + + useEffect(() => { + function fullscreenChangeHandler() { + setIsFullscreen(!!document.fullscreenElement); + } + + document.addEventListener("fullscreenchange", fullscreenChangeHandler); + return () => document.removeEventListener("fullscreenchange", fullscreenChangeHandler); + }, []); + useEffect(() => { const handleResize = () => { setIsTablet(window.innerWidth >= 768); @@ -259,6 +270,70 @@ function CafePage({ } } }; +const FullscreenButton = ({ onClick }) => { + return ( +

+ + <> + +
+ ); +}; +const handleFullscreen = () => { + const elem = document.documentElement; // fullscreen seluruh halaman + + if (!document.fullscreenElement) { + // masuk fullscreen + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.mozRequestFullScreen) { /* Firefox */ + elem.mozRequestFullScreen(); + } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari & Opera */ + elem.webkitRequestFullscreen(); + } else if (elem.msRequestFullscreen) { /* IE/Edge */ + elem.msRequestFullscreen(); + } + } else { + // keluar fullscreen + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozCancelFullScreen) { /* Firefox */ + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */ + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { /* IE/Edge */ + document.msExitFullscreen(); + } + } +}; if (loading) return ( @@ -297,6 +372,7 @@ function CafePage({ )}
+{isTablet && !isFullscreen && }
{ + console.log(transaction) + const formatWaktu = (() => { + const date = transaction?.createdAt + ? new Date(transaction.createdAt) + : new Date(); // UTC now + const tanggal = date.toLocaleDateString("id-ID"); + const jam = date.toLocaleTimeString("id-ID", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); + return `${tanggal} ${jam}`; + })(); + + + const itemsStr = transaction.DetailedTransactions.map((dt) => { + const name = + dt.Item.name.length > 11 + ? dt.Item.name.slice(0, 11) + : dt.Item.name.padEnd(11); + const qty = dt.qty.toString().padStart(3); + const total = formatRupiah(dt.qty * (dt.promoPrice || dt.price)).padStart(15); + return `${name} ${qty} ${total}`; + }).join("\n"); + + const totalHarga = transaction.DetailedTransactions.reduce((acc, dt) => { + return acc + dt.qty * (dt.promoPrice || dt.price); + }, 0); + + const totalStr = `Total: ${formatRupiah(totalHarga)}`; + + const receiptText = ` CAFE HOREE + Jl. Ahmad Yani No. 12, Kediri + Telp: 0812-1617-6963 + +============================== +Tanggal : ${formatWaktu} +Bayar : ${transaction.payment_type} +------------------------------ +Item Qty Total +------------------------------ +${itemsStr} +${totalStr} +============================== + Terima kasih atas kunjungannya! + ~~ + supported by kedaimaster.com + + +\n\n\n\n\n`; + + const params = new URLSearchParams(); + params.append("content", receiptText); + params.append("encode_format", "UTF-8"); + + const printUrl = `btprinter://print?${params.toString()}`; + + window.location.href = printUrl; + }; + + const formatRupiah = (value) => { + if (typeof value !== "number") return value; + return value.toLocaleString("id-ID", { + style: "currency", + currency: "IDR", + minimumFractionDigits: 0, + }); + }; const handlePay = async (orderMethod) => { setIsPaymentLoading(true); @@ -300,9 +369,16 @@ export default function Invoice({ tableNumber, textareaRef.current.value ); - if (pay) window.location.reload(); - - } else if (deviceType == "guestSide") { + if (pay) { + handlePrint(pay.transaction); + localStorage.removeItem("cart"); + localStorage.removeItem("lastTransaction"); + setCartItems([]); + setTotalPrice(0); + window.dispatchEvent(new Event("localStorageUpdated")); + } + } + else if (deviceType == "guestSide") { const pay = await handlePaymentFromGuestSide( shopId, email, @@ -429,15 +505,15 @@ export default function Invoice({ }} > {!isTablet && - - - -} + + + + } Keranjang
@@ -452,7 +528,7 @@ export default function Invoice({ alignItems: "center", }} > -
+
@@ -671,7 +747,7 @@ export default function Invoice({ return ( total + (transaction.promoPrice == 0 || - transaction.promoPrice == null + transaction.promoPrice == null ? transaction.price * transaction.qty : transaction.promoPrice * transaction.qty) ); diff --git a/src/pages/PrintPage.js b/src/pages/PrintPage.js index f0805bb..e04e924 100644 --- a/src/pages/PrintPage.js +++ b/src/pages/PrintPage.js @@ -1,13 +1,12 @@ import React, { useState, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import qs from 'qs'; -import '../print.css'; // Kamu bisa pakai styling temanmu atau buat file baru +import '../print.css'; export default function PrintPage() { const location = useLocation(); const [orientation, setOrientation] = useState('portrait'); - // Parse data dari query string const data = useMemo(() => { try { const query = qs.parse(location.search, { ignoreQueryPrefix: true }); @@ -19,8 +18,57 @@ export default function PrintPage() { if (!data) return
Invalid data
; - const handlePrint = () => { - window.print(); + const formatWaktu = (() => { + const date = new Date(data.date); + const tanggal = date.toLocaleDateString('id-ID'); + const jam = date.toLocaleTimeString('id-ID', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + return `${tanggal} ${jam}`; + })(); + + const itemsStr = data.items.map((item) => { + const name = item.name.length > 11 ? item.name.slice(0, 11) : item.name.padEnd(11); + const qty = item.qty.toString().padStart(2); + const total = (item.qty * item.price).toString().padStart(6); + return `${name} ${qty} ${total}`; + }).join('\n'); + + const getReceiptText = () => { + return ( +` CAFE HOREE + Jl. Merdeka No. 123, Jakarta + Telp: (021) 12345678 + +============================== +Tanggal : ${formatWaktu} +Kasir : ${data.cashier || 'UNKNOWN'} +Bayar : ${data.payment_type} +------------------------------ +Item Q Total +------------------------------ +${itemsStr} +------------------------------ +Terima kasih atas kunjungan Anda! + ~ Cafe Horee ~ + www.kedaimaster.com` + ); + }; + + const handlePrintBluetooth = () => { + const content = getReceiptText(); + + const params = new URLSearchParams(); + params.append("content", content); + params.append("encode_format", "UTF-8"); + + // Optional: jika ingin spesifik printer Bluetooth + // params.append("device_address", "00:11:22:33:44:55"); + + const printUrl = `btprinter://print?${params.toString()}`; + window.location.href = printUrl; }; return ( @@ -41,85 +89,14 @@ export default function PrintPage() { Landscape
-
-
-

Struk Pembayaran

-

Transaction ID: - {data.transactionId}

-

Waktu: { - (() => { - const date = new Date(data.date); - const options = { day: '2-digit', month: 'long', year: 'numeric' }; - const tanggal = date.toLocaleDateString('id-ID', options); - const jam = date.toLocaleTimeString('id-ID', { - hour: '2-digit', - minute: '2-digit', - hour12: false - }); - return `${tanggal}, ${jam}`; - })() - }

- - {/*

Table: - {data.table}

*/} -

Metode Pembayaran: - {data.payment_type}

-
- {data.items.map((item, idx) => ( -

-

{item.name} x {item.qty} Rp{item.price.toLocaleString('id-ID')}
-

- ))} -
-

Total: Rp{data.total.toLocaleString('id-ID')}

-
-

Terima kasih atas kunjungan Anda!

-
- -
- ~ Cafe Horee ~ -
- -
-
-

www.kedaimaster.com

-
+
+                {getReceiptText()}
+            
); } diff --git a/src/pages/Transactions.js b/src/pages/Transactions.js index 297305c..0acd9d9 100644 --- a/src/pages/Transactions.js +++ b/src/pages/Transactions.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react";import qs from 'qs'; +import React, { useEffect, useState } from "react"; import qs from 'qs'; import styles from "./Transactions.module.css"; import { useParams, useNavigate } from "react-router-dom"; @@ -62,6 +62,12 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price); }, 0); }; + +const formatRupiah = (number) => { + return 'Rp' + number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); +}; + + const calculateAllTransactionsTotal = (transactions) => { return transactions .filter(transaction => transaction.confirmed > 1) // Filter transactions where confirmed > 1 @@ -147,26 +153,63 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev }; const handlePrint = (transaction) => { - // Pilih data yang ingin dikirim - const printableData = { - transactionId: transaction?.transactionId, - items: transaction?.DetailedTransactions.map(dt => ({ - name: dt.Item.name, - qty: dt.qty, - price: dt.promoPrice || dt.price, - })), - total: calculateTotalPrice(transaction.DetailedTransactions), - date: transaction.createdAt, - payment_type: transaction.payment_type, - table: transaction.Table?.tableNo || "N/A", - }; + const formatWaktu = (() => { + const date = new Date(transaction.createdAt); + const tanggal = date.toLocaleDateString('id-ID'); + const jam = date.toLocaleTimeString('id-ID', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + return `${tanggal} ${jam}`; + })(); - // Serialize to query string - const queryString = qs.stringify({ data: JSON.stringify(printableData) }); + const itemsStr = transaction.DetailedTransactions.map((dt) => { + const name = dt.Item.name.length > 11 + ? dt.Item.name.slice(0, 11) + : dt.Item.name.padEnd(11); + const qty = dt.qty.toString().padStart(3); + const total = formatRupiah(dt.qty * (dt.promoPrice || dt.price)).padStart(15); + return `${name} ${qty} ${total}`; + }).join('\n'); - // Navigate to /print with query string - navigate(`/${shopIdentifier}/print?${queryString}`); + const totalHarga = calculateTotalPrice(transaction.DetailedTransactions); + + const totalStr = `Total: ${formatRupiah(totalHarga)}`; + + const receiptText = ( + ` CAFE HOREE + Jl. Ahmad Yani No. 12, Kediri + Telp: 0812-1617-6963 + +============================== +Tanggal : ${formatWaktu} +Bayar : ${transaction.payment_type} +------------------------------ +Item Qty Total +------------------------------ +${itemsStr} +${totalStr} +============================== + Terima kasih atas kunjungannya! + ~~ + supported by kedaimaster.com + + +\n\n\n\n\n` + ); + + const params = new URLSearchParams(); + params.append("content", receiptText); + params.append("encode_format", "UTF-8"); + + const printUrl = `btprinter://print?${params.toString()}`; + + // Trigger aplikasi printer via URL scheme + window.location.href = printUrl; }; + + if (loading) return (
@@ -179,7 +222,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev return (

- Transaksi selesai Rp {calculateAllTransactionsTotal(transactions)} + Transaksi selesai {formatRupiah(calculateAllTransactionsTotal(transactions))}

Total: - Rp {item.totalPrice} + {formatRupiah(item.totalPrice)}
))} @@ -329,8 +372,11 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
    {transaction.DetailedTransactions.map((detail) => (
  • - {detail.Item.name} - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp - ${detail.promoPrice ? detail.promoPrice : detail.price}`} + {detail.Item.name} - {detail.qty < 1 + ? 'tidak tersedia' + : `${detail.qty} x ${formatRupiah(detail.promoPrice || detail.price)}` + } +
  • ))}
@@ -375,7 +421,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
Total: - Rp {calculateTotalPrice(transaction.DetailedTransactions)} + {formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
@@ -404,20 +450,18 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev } - {deviceType == 'clerk' && transaction.confirmed > 1 && ( -
handlePrint(transaction)} - > - Cetak struk -
- )} + {deviceType == 'clerk' && transaction.confirmed > 1 && ( +
handlePrint(transaction)} + > + Cetak struk +
+ )} {deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' && handleConfirm(transaction.transactionId)} diff --git a/src/print.css b/src/print.css index b721fad..226e566 100644 --- a/src/print.css +++ b/src/print.css @@ -3,13 +3,12 @@ flex-direction: column; align-items: center; padding: 20px; - min-height: 100vh; background-color: #f5f5f5; - font-family: Arial, sans-serif; + font-family: monospace; } .controls { - background-color: #2c3e50; + background-color: #333; color: white; padding: 20px; border-radius: 10px; @@ -19,121 +18,67 @@ margin-bottom: 30px; } -.controls h1 { - margin-top: 0; -} - -.orientation-selector { - margin: 15px 0; -} - -.orientation-selector button { - background-color: #61dafb; - color: #282c34; - border: none; - padding: 12px 24px; - font-size: 18px; - cursor: pointer; - transition: all 0.3s; - margin: 0 10px; - border-radius: 5px; +.orientation-selector button, +.print-button { + margin: 10px; + padding: 10px 20px; font-weight: bold; + cursor: pointer; + border: none; + border-radius: 5px; } .orientation-selector button.active { - background-color: #21a9c7; + background-color: #007bff; color: white; - transform: scale(1.05); -} - -.orientation-selector button:hover:not(.active) { - background-color: #4bc5e0; -} - -.print-button { - background-color: #3498db; - color: white; - border: none; - padding: 12px 24px; - font-size: 18px; - border-radius: 5px; - cursor: pointer; - margin-top: 15px; - transition: background-color 0.3s; -} - -.print-button:hover { - background-color: #2980b9; } .print-area { - background-color: white; - padding: 30px; - border-radius: 10px; - box-shadow: 0 4px 8px rgba(0,0,0,0.1); - width: 90%; - max-width: 800px; + background: white; + white-space: pre-wrap; + font-family: monospace; + font-size: 12px; + line-height: 1.4; + padding: 10px; + width: 48mm; + max-width: 48mm; color: black; + box-shadow: none; + border: 1px solid #ccc; } -/* Orientation styles */ -.print-test.portrait .print-area { - max-width: 800px; -} - -.print-test.landscape .print-area { - max-width: 1100px; -} - -/* Print specific styles */ +/* Print media query */ @media print { - @page { - margin: 58mm; - } - .print-test.portrait @page { - size: portrait; - } - .print-test.landscape @page { - size: landscape; - } body { background-color: white; margin: 0; padding: 0; } + .controls { display: none; } + .print-area { - box-shadow: none; width: 100%; max-width: 100%; - padding: 15mm; + border: none; + box-shadow: none; + padding: 0; + font-size: 12px; } + body * { visibility: hidden; } + .print-area, .print-area * { visibility: visible; } + .print-area { position: absolute; left: 0; top: 0; - width: 100%; - height: 100%; } - .print-area { - font-size: 12px; /* lebih kecil dari default */ - line-height: 1.4; -} - -.print-area h2 { - font-size: 16px; -} - -.print-area h3 { - font-size: 14px; -} - }