ok
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import Chart from "react-apexcharts";
|
import Chart from "react-apexcharts";
|
||||||
import styles from "./BarChart.module.css"; // Import the CSS module
|
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);
|
let totalValue = seriesData.reduce((acc, val) => acc + val, 0);
|
||||||
return {
|
return {
|
||||||
date: new Date(dayData.date).toLocaleDateString(),
|
date: dayData.date,
|
||||||
categories,
|
categories,
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@@ -113,15 +114,11 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
const date = new Date(dateString);
|
const d = dayjs(dateString, ["YYYY-MM-DD", "YYYY-MM-DDTHH:mm:ssZ"]);
|
||||||
const monthNames = [
|
return { month: d.format("MMM"), day: d.format("D") };
|
||||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
||||||
];
|
|
||||||
const month = monthNames[date.getMonth()];
|
|
||||||
const day = date.getDate();
|
|
||||||
return { month, day };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
<div className={`${styles.chartItemContainer} ${selectedIndex !== -1 ? styles.expanded : ''}`}>
|
||||||
|
|
||||||
@@ -142,27 +139,37 @@ const DailyCharts = ({ incomeGraph, transactionGraph, materialGraph, colors, typ
|
|||||||
key={indexx}
|
key={indexx}
|
||||||
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||||
}`}
|
}`}
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative', width: 'calc(100% / 7)' }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
type == 'yesterday' && selectedIndex == -1 || type != 'yesterday' && selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1)
|
type == 'yesterday' && selectedIndex == -1 || type != 'yesterday' && selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div style={{ position: 'absolute', bottom: '28px', left: '10%', right: '10%', borderBottom: index == indexx ? `2px solid ${colors[index % colors.length]}` : 'none' }}></div>
|
<div style={{ position: 'absolute', bottom: '21px', left: '10%', right: '10%', borderBottom: index == indexx ? `2px solid ${colors[index % colors.length]}` : 'none' }}></div>
|
||||||
<div
|
<div
|
||||||
style={{ color: index === indexx ? 'black' : 'transparent' }}>
|
style={{ color: index === indexx ? 'black' : 'transparent' }}>
|
||||||
{indexx !== chartData.length - 1 ? (
|
{indexx !== chartData.length - 1 ? (
|
||||||
<>
|
<p style={{ fontSize: '13px' }}>{day}{" "}
|
||||||
{day}{" "}
|
{(
|
||||||
{(indexx === 0 || (formatDate(chartData[indexx - 1].date).month !== month && type != 'weekly')) && month}
|
indexx === 0 ||
|
||||||
</>
|
(indexx > 0 &&
|
||||||
|
dayjs(chartData[indexx - 1].date).month() !== dayjs(item.date).month() &&
|
||||||
|
type !== "weekly")
|
||||||
|
) && month}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<p style={{ fontSize: '13px' }}>
|
||||||
{type != 'weekly' ? 'Hari ini' : day}
|
{type != 'weekly' ? 'Hari ini' : day + ' ' + month}
|
||||||
</>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{index == indexx && <p style={{ margin: '7px 0 0 0', fontSize: '12px', color: 'black' }}>
|
{index == indexx && <p style={{
|
||||||
|
margin: '7px 0 0 0', fontSize: '9px', color: 'black',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
left: 0,
|
||||||
|
bottom: 6
|
||||||
|
}}>
|
||||||
{graphFilter === 'transactions'
|
{graphFilter === 'transactions'
|
||||||
? chartData[indexx].totalValue
|
? chartData[indexx].totalValue
|
||||||
: formatRupiah(chartData[indexx].totalValue)}
|
: formatRupiah(chartData[indexx].totalValue)}
|
||||||
|
|||||||
@@ -252,10 +252,9 @@ export const handlePaymentFromClerk = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Handle success response
|
const data = await response.json();
|
||||||
console.log("Transaction successful!");
|
console.log("Transaction successful!", data);
|
||||||
// Optionally return response data or handle further actions upon success
|
return data;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
// Handle error response
|
// Handle error response
|
||||||
console.error("Transaction failed:", response.statusText);
|
console.error("Transaction failed:", response.statusText);
|
||||||
|
|||||||
@@ -103,6 +103,17 @@ function CafePage({
|
|||||||
// };
|
// };
|
||||||
const [isTablet, setIsTablet] = useState(window.innerWidth >= 768);
|
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(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
setIsTablet(window.innerWidth >= 768);
|
setIsTablet(window.innerWidth >= 768);
|
||||||
@@ -259,6 +270,70 @@ function CafePage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const FullscreenButton = ({ onClick }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#7272729e",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1000
|
||||||
|
}}
|
||||||
|
title="Toggle Fullscreen"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
transform: "rotate(45deg)",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 24,
|
||||||
|
lineHeight: 1,
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
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)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
@@ -297,6 +372,7 @@ function CafePage({
|
|||||||
)}
|
)}
|
||||||
<div style={{ width: isTablet ? "60%" : "100%" }}>
|
<div style={{ width: isTablet ? "60%" : "100%" }}>
|
||||||
<div className="App-header">
|
<div className="App-header">
|
||||||
|
{isTablet && !isFullscreen && <FullscreenButton onClick={handleFullscreen} />}
|
||||||
<Header
|
<Header
|
||||||
HeaderText={"Menu"}
|
HeaderText={"Menu"}
|
||||||
showProfile={true}
|
showProfile={true}
|
||||||
|
|||||||
@@ -275,6 +275,75 @@ export default function Invoice({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handlePrint = (transaction) => {
|
||||||
|
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) => {
|
const handlePay = async (orderMethod) => {
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
@@ -300,9 +369,16 @@ export default function Invoice({
|
|||||||
tableNumber,
|
tableNumber,
|
||||||
textareaRef.current.value
|
textareaRef.current.value
|
||||||
);
|
);
|
||||||
if (pay) window.location.reload();
|
if (pay) {
|
||||||
|
handlePrint(pay.transaction);
|
||||||
} else if (deviceType == "guestSide") {
|
localStorage.removeItem("cart");
|
||||||
|
localStorage.removeItem("lastTransaction");
|
||||||
|
setCartItems([]);
|
||||||
|
setTotalPrice(0);
|
||||||
|
window.dispatchEvent(new Event("localStorageUpdated"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (deviceType == "guestSide") {
|
||||||
const pay = await handlePaymentFromGuestSide(
|
const pay = await handlePaymentFromGuestSide(
|
||||||
shopId,
|
shopId,
|
||||||
email,
|
email,
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import '../print.css'; // Kamu bisa pakai styling temanmu atau buat file baru
|
import '../print.css';
|
||||||
|
|
||||||
export default function PrintPage() {
|
export default function PrintPage() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [orientation, setOrientation] = useState('portrait');
|
const [orientation, setOrientation] = useState('portrait');
|
||||||
|
|
||||||
// Parse data dari query string
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
||||||
@@ -19,8 +18,57 @@ export default function PrintPage() {
|
|||||||
|
|
||||||
if (!data) return <div>Invalid data</div>;
|
if (!data) return <div>Invalid data</div>;
|
||||||
|
|
||||||
const handlePrint = () => {
|
const formatWaktu = (() => {
|
||||||
window.print();
|
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 (
|
return (
|
||||||
@@ -41,85 +89,14 @@ export default function PrintPage() {
|
|||||||
Landscape
|
Landscape
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="print-button" onClick={handlePrint}>
|
<button className="print-button" onClick={handlePrintBluetooth}>
|
||||||
Cetak Struk ({orientation})
|
🖨️ Print ke Bluetooth
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="print-area">
|
<pre className="print-area">
|
||||||
<h2>Struk Pembayaran</h2>
|
{getReceiptText()}
|
||||||
<p><strong>Transaction ID:
|
</pre>
|
||||||
</strong> {data.transactionId}</p>
|
|
||||||
<p><strong>Waktu:</strong> {
|
|
||||||
(() => {
|
|
||||||
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}`;
|
|
||||||
})()
|
|
||||||
}</p>
|
|
||||||
|
|
||||||
{/* <p><strong>Table:
|
|
||||||
</strong> {data.table}</p> */}
|
|
||||||
<p><strong>Metode Pembayaran:
|
|
||||||
</strong> {data.payment_type}</p>
|
|
||||||
<div>
|
|
||||||
{data.items.map((item, idx) => (
|
|
||||||
<p key={idx} style={{ marginBottom: '12px' }}>
|
|
||||||
<div>{item.name} x {item.qty} Rp{item.price.toLocaleString('id-ID')}</div>
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<h3>Total: Rp{data.total.toLocaleString('id-ID')}</h3>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: '24px',
|
|
||||||
fontStyle: 'italic',
|
|
||||||
fontSize: '12px',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p style={{ margin: 0 }}>Terima kasih atas kunjungan Anda!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontStyle: 'italic',
|
|
||||||
fontSize: '12px',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
|
|
||||||
textAlign: 'center'
|
|
||||||
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong style={{
|
|
||||||
marginLeft: '-60px'
|
|
||||||
|
|
||||||
}}>~ Cafe Horee ~</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: '24px',
|
|
||||||
fontStyle: 'italic',
|
|
||||||
fontSize: '12px',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
textAlign: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<p style={{
|
|
||||||
fontSize: '9px',
|
|
||||||
marginBottom: '30px',
|
|
||||||
marginLeft: '60px',
|
|
||||||
|
|
||||||
}}>www.kedaimaster.com</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
|
return total + dt.qty * (dt.promoPrice ? dt.promoPrice : dt.price);
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatRupiah = (number) => {
|
||||||
|
return 'Rp' + number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const calculateAllTransactionsTotal = (transactions) => {
|
const calculateAllTransactionsTotal = (transactions) => {
|
||||||
return transactions
|
return transactions
|
||||||
.filter(transaction => transaction.confirmed > 1) // Filter transactions where confirmed > 1
|
.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) => {
|
const handlePrint = (transaction) => {
|
||||||
// Pilih data yang ingin dikirim
|
const formatWaktu = (() => {
|
||||||
const printableData = {
|
const date = new Date(transaction.createdAt);
|
||||||
transactionId: transaction?.transactionId,
|
const tanggal = date.toLocaleDateString('id-ID');
|
||||||
items: transaction?.DetailedTransactions.map(dt => ({
|
const jam = date.toLocaleTimeString('id-ID', {
|
||||||
name: dt.Item.name,
|
hour: '2-digit',
|
||||||
qty: dt.qty,
|
minute: '2-digit',
|
||||||
price: dt.promoPrice || dt.price,
|
hour12: false,
|
||||||
})),
|
});
|
||||||
total: calculateTotalPrice(transaction.DetailedTransactions),
|
return `${tanggal} ${jam}`;
|
||||||
date: transaction.createdAt,
|
})();
|
||||||
payment_type: transaction.payment_type,
|
|
||||||
table: transaction.Table?.tableNo || "N/A",
|
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 = 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serialize to query string
|
|
||||||
const queryString = qs.stringify({ data: JSON.stringify(printableData) });
|
|
||||||
|
|
||||||
// Navigate to /print with query string
|
|
||||||
navigate(`/${shopIdentifier}/print?${queryString}`);
|
|
||||||
};
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
<div className="Loader">
|
<div className="Loader">
|
||||||
@@ -179,7 +222,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
return (
|
return (
|
||||||
<div className={styles.Transactions}>
|
<div className={styles.Transactions}>
|
||||||
<h2 className={styles["Transactions-title"]}>
|
<h2 className={styles["Transactions-title"]}>
|
||||||
Transaksi selesai Rp {calculateAllTransactionsTotal(transactions)}
|
Transaksi selesai {formatRupiah(calculateAllTransactionsTotal(transactions))}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -208,7 +251,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
</ul>
|
</ul>
|
||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<span>Total:</span>
|
<span>Total:</span>
|
||||||
<span>Rp {item.totalPrice}</span>
|
<span>{formatRupiah(item.totalPrice)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -329,8 +372,11 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
<ul>
|
<ul>
|
||||||
{transaction.DetailedTransactions.map((detail) => (
|
{transaction.DetailedTransactions.map((detail) => (
|
||||||
<li key={detail.detailedTransactionId}>
|
<li key={detail.detailedTransactionId}>
|
||||||
<span>{detail.Item.name}</span> - {detail.qty < 1 ? 'tidak tersedia' : `${detail.qty} x Rp
|
<span>{detail.Item.name}</span> - {detail.qty < 1
|
||||||
${detail.promoPrice ? detail.promoPrice : detail.price}`}
|
? 'tidak tersedia'
|
||||||
|
: `${detail.qty} x ${formatRupiah(detail.promoPrice || detail.price)}`
|
||||||
|
}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -375,7 +421,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
<div className={styles.TotalContainer}>
|
<div className={styles.TotalContainer}>
|
||||||
<span>Total:</span>
|
<span>Total:</span>
|
||||||
<span>
|
<span>
|
||||||
Rp {calculateTotalPrice(transaction.DetailedTransactions)}
|
{formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -415,9 +461,7 @@ export default function Transactions({ shop, shopId, propsShopId, sendParam, dev
|
|||||||
{deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
|
{deviceType == 'guestDevice' && transaction.confirmed < 2 && transaction.payment_type != 'cash' && transaction.payment_type != 'paylater/cash' &&
|
||||||
<ButtonWithReplica
|
<ButtonWithReplica
|
||||||
paymentUrl={paymentUrl}
|
paymentUrl={paymentUrl}
|
||||||
price={
|
price={formatRupiah(calculateTotalPrice(transaction.DetailedTransactions))}
|
||||||
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
|
|
||||||
}
|
|
||||||
disabled={isPaymentLoading}
|
disabled={isPaymentLoading}
|
||||||
isPaymentLoading={isPaymentLoading}
|
isPaymentLoading={isPaymentLoading}
|
||||||
handleClick={() => handleConfirm(transaction.transactionId)}
|
handleClick={() => handleConfirm(transaction.transactionId)}
|
||||||
|
|||||||
115
src/print.css
115
src/print.css
@@ -3,13 +3,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
font-family: Arial, sans-serif;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
background-color: #2c3e50;
|
background-color: #333;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -19,121 +18,67 @@
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls h1 {
|
.orientation-selector button,
|
||||||
margin-top: 0;
|
.print-button {
|
||||||
}
|
margin: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
.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;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.orientation-selector button.active {
|
.orientation-selector button.active {
|
||||||
background-color: #21a9c7;
|
background-color: #007bff;
|
||||||
color: white;
|
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 {
|
.print-area {
|
||||||
background-color: white;
|
background: white;
|
||||||
padding: 30px;
|
white-space: pre-wrap;
|
||||||
border-radius: 10px;
|
font-family: monospace;
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
font-size: 12px;
|
||||||
width: 90%;
|
line-height: 1.4;
|
||||||
max-width: 800px;
|
padding: 10px;
|
||||||
|
width: 48mm;
|
||||||
|
max-width: 48mm;
|
||||||
color: black;
|
color: black;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Orientation styles */
|
/* Print media query */
|
||||||
.print-test.portrait .print-area {
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-test.landscape .print-area {
|
|
||||||
max-width: 1100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print specific styles */
|
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
|
||||||
margin: 58mm;
|
|
||||||
}
|
|
||||||
.print-test.portrait @page {
|
|
||||||
size: portrait;
|
|
||||||
}
|
|
||||||
.print-test.landscape @page {
|
|
||||||
size: landscape;
|
|
||||||
}
|
|
||||||
body {
|
body {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-area {
|
.print-area {
|
||||||
box-shadow: none;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 15mm;
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body * {
|
body * {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-area, .print-area * {
|
.print-area, .print-area * {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-area {
|
.print-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user