26/02/2025
This commit is contained in:
parent
11d418602a
commit
492552d373
@ -2,13 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/queue-white.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Poppins Font -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
<title>Vite + React</title>
|
||||
<title>Antrian Apps</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.29.0",
|
||||
@ -4166,6 +4167,15 @@
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@ -7852,6 +7862,12 @@
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
|
||||
},
|
||||
"react-icons": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.29.0",
|
||||
|
||||
7
public/queue-white.svg
Normal file
7
public/queue-white.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve" stroke="#ffffff">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <path d="M67.6,20.1c-2.9,0-5.4,2.1-6.4,5c3.8,1.7,6.7,5.4,7.7,9.9c3.1-0.7,5.4-3.7,5.4-7.3 C74.3,23.4,71.3,20.1,67.6,20.1z"/> <path d="M73.8,37.9c-1.3,1.2-2.9,2-4.7,2.3c-0.3,2.7-1.3,5.2-2.8,7.2h11.1c1.4,0,2.5-1.1,2.5-2.5v-1.2 C79.9,41,76.9,39.3,73.8,37.9z"/> <path d="M63.2,50c-2,1.8-4.5,2.9-7.2,2.9c-0.2,3.1-1.1,5.9-2.5,8.3h14c1.6,0,3-1.3,3-3V57 C70.4,53.7,66.8,51.7,63.2,50z"/> <path d="M50.3,65.2c-2.8,2.8-6.6,4.4-10.6,4.4c-4.1,0-7.9-1.7-10.7-4.6C24.4,67.2,20,70,20,74.1V76 c0,2.2,1.8,3.9,3.9,3.9h31.3c2.2,0,3.9-1.8,3.9-3.9v-1.8C59.1,70,54.9,67.3,50.3,65.2z"/> <ellipse cx="39.6" cy="51.6" rx="10.7" ry="11.8"/> <path d="M55.7,29.5c-3.7,0-6.8,2.7-7.7,6.5c3.7,2.4,6.4,6.5,7.5,11.2c0.1,0,0.1,0,0.2,0c4.4,0,8-4,8-8.9 C63.8,33.5,60.2,29.5,55.7,29.5z"/> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
15
public/queue.svg
Normal file
15
public/queue.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800px" height="800px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<path d="M67.6,20.1c-2.9,0-5.4,2.1-6.4,5c3.8,1.7,6.7,5.4,7.7,9.9c3.1-0.7,5.4-3.7,5.4-7.3
|
||||
C74.3,23.4,71.3,20.1,67.6,20.1z"/>
|
||||
<path d="M73.8,37.9c-1.3,1.2-2.9,2-4.7,2.3c-0.3,2.7-1.3,5.2-2.8,7.2h11.1c1.4,0,2.5-1.1,2.5-2.5v-1.2
|
||||
C79.9,41,76.9,39.3,73.8,37.9z"/>
|
||||
<path d="M63.2,50c-2,1.8-4.5,2.9-7.2,2.9c-0.2,3.1-1.1,5.9-2.5,8.3h14c1.6,0,3-1.3,3-3V57
|
||||
C70.4,53.7,66.8,51.7,63.2,50z"/>
|
||||
<path d="M50.3,65.2c-2.8,2.8-6.6,4.4-10.6,4.4c-4.1,0-7.9-1.7-10.7-4.6C24.4,67.2,20,70,20,74.1V76
|
||||
c0,2.2,1.8,3.9,3.9,3.9h31.3c2.2,0,3.9-1.8,3.9-3.9v-1.8C59.1,70,54.9,67.3,50.3,65.2z"/>
|
||||
<ellipse cx="39.6" cy="51.6" rx="10.7" ry="11.8"/>
|
||||
<path d="M55.7,29.5c-3.7,0-6.8,2.7-7.7,6.5c3.7,2.4,6.4,6.5,7.5,11.2c0.1,0,0.1,0,0.2,0c4.4,0,8-4,8-8.9
|
||||
C63.8,33.5,60.2,29.5,55.7,29.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
13
src/components/Admin/AdminFooter.jsx
Normal file
13
src/components/Admin/AdminFooter.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Container } from "react-bootstrap";
|
||||
|
||||
const AdminFooter = () => {
|
||||
return (
|
||||
<footer className="bg-dark text-center text-light py-3 mt-auto">
|
||||
<Container>
|
||||
<p className="mb-0">© {new Date().getFullYear()} Admin Dashboard</p>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminFooter;
|
||||
13
src/components/Admin/AdminNavbar.jsx
Normal file
13
src/components/Admin/AdminNavbar.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Navbar, Container } from "react-bootstrap";
|
||||
|
||||
const AdminNavbar = () => {
|
||||
return (
|
||||
<Navbar bg="dark" variant="dark" expand="lg">
|
||||
<Container>
|
||||
<Navbar.Brand href="/admin">Admin Dashboard</Navbar.Brand>
|
||||
</Container>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminNavbar;
|
||||
77
src/components/Admin/AdminSidebar.jsx
Normal file
77
src/components/Admin/AdminSidebar.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { useState } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { FaList, FaPhone, FaChartBar, FaCog, FaTv, FaThList, FaChevronDown, FaChevronRight, FaTools } from "react-icons/fa";
|
||||
|
||||
const AdminSidebar = () => {
|
||||
const [openMenu, setOpenMenu] = useState(null);
|
||||
|
||||
const toggleMenu = (menu) => {
|
||||
setOpenMenu(openMenu === menu ? null : menu);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column bg-dark text-white vh-100 p-3" style={{ width: "250px" }}>
|
||||
<h5 className="mb-4">Admin Menu</h5>
|
||||
|
||||
{/* Antrian Menu */}
|
||||
<div>
|
||||
<button
|
||||
onClick={() => toggleMenu("antrian")}
|
||||
className="btn btn-secondary w-100 text-start mb-2 d-flex align-items-center justify-content-between"
|
||||
>
|
||||
<span><FaList className="me-2" /> Antrian</span>
|
||||
{openMenu === "antrian" ? <FaChevronDown /> : <FaChevronRight />}
|
||||
</button>
|
||||
{openMenu === "antrian" && (
|
||||
<div className="ms-3 my-2 d-grid gap-2">
|
||||
<NavLink to="/admin/queue-list" className="btn btn-outline-light d-flex align-items-center">
|
||||
<FaList className="me-2" /> Daftar Antrian
|
||||
</NavLink>
|
||||
<NavLink to="/admin/call-queue" className="btn btn-outline-light d-flex align-items-center">
|
||||
<FaPhone className="me-2" /> Panggil Antrian
|
||||
</NavLink>
|
||||
{/* <NavLink to="/admin/queue-report" className="btn btn-outline-light d-flex align-items-center">
|
||||
<FaChartBar className="me-2" /> Laporan Antrian
|
||||
</NavLink> */}
|
||||
<NavLink to="/admin/queue-settings-display" className="btn btn-outline-light d-flex align-items-center">
|
||||
<FaCog className="me-2" /> Pengaturan Layar
|
||||
</NavLink>
|
||||
<NavLink to="/admin/queue-settings-menu" className="btn btn-outline-light d-flex align-items-center">
|
||||
<FaTools className="me-2" /> Pengaturan Menu
|
||||
</NavLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Monitor Menu */}
|
||||
<div>
|
||||
<button
|
||||
onClick={() => toggleMenu("monitor")}
|
||||
className="btn btn-secondary w-100 text-start mb-2 d-flex align-items-center justify-content-between"
|
||||
>
|
||||
<span><FaTv className="me-2" /> Monitor</span>
|
||||
{openMenu === "monitor" ? <FaChevronDown /> : <FaChevronRight />}
|
||||
</button>
|
||||
{openMenu === "monitor" && (
|
||||
<div className="ms-3 my-2 d-grid gap-2">
|
||||
<button
|
||||
onClick={() => window.open("/queue-display", "_blank")}
|
||||
className="btn btn-outline-light d-flex align-items-center"
|
||||
>
|
||||
<FaTv className="me-2" /> Tampilan Antrian
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.open("/queue-menu", "_blank")}
|
||||
className="btn btn-outline-light d-flex align-items-center"
|
||||
>
|
||||
<FaThList className="me-2" /> Menu Antrian
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminSidebar;
|
||||
119
src/components/Admin/QueueActions.jsx
Normal file
119
src/components/Admin/QueueActions.jsx
Normal file
@ -0,0 +1,119 @@
|
||||
const queueData = [
|
||||
{ id: 1, name: "John Doe", phone: "08123456789", service: "Siap Print", serviceId: "J0001", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default"},
|
||||
{ id: 2, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep"},
|
||||
{ id: 3, name: "Michael Johnson", phone: "08345678901", service: "FotoCopy", serviceId: "J0003", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell"},
|
||||
{ id: 4, name: "Emily Brown", phone: "08456789012", service: "Tamu", serviceId: "J0006", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime"},
|
||||
{ id: 5, name: "David Wilson", phone: "08567890123", service: "Online", serviceId: "J0005", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding"},
|
||||
{ id: 6, name: "Sophia Martinez", phone: "08678901234", service: "Online", serviceId: "J0005", locket: "06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Selesai", called: false, active: true, audio: "Ping"},
|
||||
{ id: 7, name: "James Anderson", phone: "08789012345", service: "Siap Print", serviceId: "J0001", locket: "03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true, audio: "Chirp"},
|
||||
{ id: 8, name: "Olivia Thomas", phone: "08890123456", service: "Design", serviceId: "J0002", locket: "07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Dilayani", called: false, active: true, audio: "Ring"},
|
||||
{ id: 9, name: "Liam White", phone: "08901234567", service: "FotoCopy", serviceId: "J0003", locket: "09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Selesai", called: false, active: true, audio: "Tone"},
|
||||
{ id: 10, name: "Emma Harris", phone: "08012345678", service: "Siap Print", serviceId: "J0001", locket: "10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true, audio: "Alarm" },
|
||||
{ id: 11, name: "John Doe", phone: "08123456789", service: "Siap Print", serviceId: "J0001", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default"},
|
||||
{ id: 12, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep"},
|
||||
{ id: 13, name: "Michael Johnson", phone: "08345678901", service: "FotoCopy", serviceId: "J0003", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell"},
|
||||
{ id: 14, name: "Emily Brown", phone: "08456789012", service: "Online", serviceId: "J0005", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime"},
|
||||
{ id: 15, name: "David Wilson", phone: "08567890123", service: "Design", serviceId: "J0002", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding"},
|
||||
{ id: 16, name: "John Doe", phone: "08123456789", service: "Online", serviceId: "J0005", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default"},
|
||||
{ id: 17, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep"},
|
||||
{ id: 18, name: "Michael Johnson", phone: "08345678901", service: "Retur", serviceId: "J0004", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell"},
|
||||
{ id: 19, name: "Emily Brown", phone: "08456789012", service: "Online", serviceId: "J0005", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime"},
|
||||
{ id: 20, name: "David Wilson", phone: "08567890123", service: "Design", serviceId: "J0002", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding"},
|
||||
{ id: 21, name: "Sophia Martinez", phone: "08678901234", service: "Retur", serviceId: "J0004", locket: "06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Selesai", called: false, active: true, audio: "Ping"},
|
||||
{ id: 22, name: "James Anderson", phone: "08789012345", service: "Online", serviceId: "J0005", locket: "03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true, audio: "Chirp"},
|
||||
{ id: 23, name: "Olivia Thomas", phone: "08890123456", service: "Design", serviceId: "J0002", locket: "07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Dilayani", called: false, active: true, audio: "Ring"},
|
||||
{ id: 24, name: "Liam White", phone: "08901234567", service: "Tamu", serviceId: "J0006", locket: "09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Selesai", called: false, active: true, audio: "Tone"},
|
||||
{ id: 25, name: "Emma Harris", phone: "08012345678", service: "Siap Print", serviceId: "J0001", locket: "10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true, audio: "Alarm"},
|
||||
];
|
||||
|
||||
export const getQueueSettingsData = () => {
|
||||
return queueData.slice(0, 6).map(({ service, serviceId, active, id }) => ({
|
||||
id,
|
||||
service,
|
||||
serviceId,
|
||||
active,
|
||||
}));
|
||||
};
|
||||
|
||||
export const handleCall = (id) => {
|
||||
const index = queueData.findIndex((item) => item.id === id);
|
||||
if (index !== -1) {
|
||||
queueData[index].called = true;
|
||||
}
|
||||
};
|
||||
|
||||
export const handleEdit = (id) => {
|
||||
console.log("Edit data dengan ID:", id);
|
||||
};
|
||||
|
||||
export const handleDelete = (id) => {
|
||||
const index = queueData.findIndex((item) => item.id === id);
|
||||
if (index !== -1) {
|
||||
queueData.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleServiceStatus = (id) => {
|
||||
const index = queueData.findIndex((item) => item.id === id);
|
||||
if (index !== -1) {
|
||||
queueData[index].active = !queueData[index].active;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFilteredQueueData = (callQueueView = false) => {
|
||||
if (callQueueView) {
|
||||
return queueData.filter((item) => item.status === "Menunggu");
|
||||
}
|
||||
return queueData;
|
||||
};
|
||||
|
||||
export const getQueueDisplayData = () => {
|
||||
const queueData = getFilteredQueueData(false) || [];
|
||||
|
||||
// Ambil maksimal 25 data untuk animasi berjalan
|
||||
const displayedQueue = queueData.slice(0, 25);
|
||||
|
||||
return displayedQueue.map(({ queueNumber, locket }) => ({
|
||||
number: queueNumber,
|
||||
counter: locket || "-",
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
// QueueActions.js
|
||||
import { useState } from "react";
|
||||
|
||||
export const useQueueData = (settingsView, callQueueView) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const itemsPerPage = 10;
|
||||
|
||||
// Mengambil data berdasarkan kondisi tampilan
|
||||
const queueData = settingsView ? getQueueSettingsData() || [] : getFilteredQueueData(callQueueView) || [];
|
||||
|
||||
// Filter berdasarkan pencarian
|
||||
const filteredData = searchQuery
|
||||
? queueData.filter((item) =>
|
||||
(item.name?.toLowerCase() || "").includes(searchQuery.toLowerCase()) ||
|
||||
(item.service?.toLowerCase() || "").includes(searchQuery.toLowerCase())
|
||||
)
|
||||
: queueData;
|
||||
|
||||
// Pagination logic
|
||||
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
|
||||
const indexOfLastItem = currentPage * itemsPerPage;
|
||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||
const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem);
|
||||
|
||||
return {
|
||||
currentItems,
|
||||
currentPage,
|
||||
totalPages,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
setCurrentPage,
|
||||
indexOfFirstItem, // ✅ Tambahkan ini
|
||||
};
|
||||
};
|
||||
|
||||
144
src/components/Admin/QueueTable.jsx
Normal file
144
src/components/Admin/QueueTable.jsx
Normal file
@ -0,0 +1,144 @@
|
||||
import { Table, Button, Form, Pagination, InputGroup, FormControl } from "react-bootstrap";
|
||||
import { FaSearch } from "react-icons/fa";
|
||||
import { handleCall, toggleServiceStatus, handleDelete, handleEdit, useQueueData } from "./QueueActions";
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const QueueTable = ({ showActions = true, reportView = false, settingsView = false, displayView = false, callQueueView = false }) => {
|
||||
|
||||
const { currentItems, currentPage, totalPages, searchQuery, setSearchQuery, setCurrentPage, indexOfFirstItem } = useQueueData(settingsView, callQueueView);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(callQueueView || reportView || (!settingsView && !displayView)) && (
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroup.Text>
|
||||
<FaSearch />
|
||||
</InputGroup.Text>
|
||||
<FormControl
|
||||
type="text"
|
||||
placeholder="Cari nama atau layanan..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
<Table striped bordered hover responsive>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
{displayView ? (
|
||||
<>
|
||||
<th>Pengaturan Suara</th>
|
||||
<th>Video Komersil</th>
|
||||
<th className="text-center">Aksi</th>
|
||||
</>
|
||||
) : settingsView ? (
|
||||
<>
|
||||
<th>Nama Layanan</th>
|
||||
<th>ID Layanan</th>
|
||||
<th>Status</th>
|
||||
<th className="text-center">Aksi</th>
|
||||
</>
|
||||
) : reportView ? (
|
||||
<>
|
||||
<th>Nama</th>
|
||||
<th>No. Telepon</th>
|
||||
<th>Layanan</th>
|
||||
<th>Loket</th>
|
||||
<th>Cetak Antrian</th>
|
||||
<th>Selesai Antrian</th>
|
||||
<th className="text-center">Aksi</th>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<th>Nama</th>
|
||||
<th>Layanan</th>
|
||||
<th>Kode Layanan</th>
|
||||
<th>Nomor Antrian</th>
|
||||
<th>Loket</th>
|
||||
<th>Status</th>
|
||||
{showActions && <th className="text-center">Aksi</th>}
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentItems.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td>{index + 1 + indexOfFirstItem}</td>
|
||||
{displayView ? (
|
||||
<>
|
||||
<td>
|
||||
<Form.Select defaultValue={item.audio}>
|
||||
<option>Default</option>
|
||||
<option>Beep</option>
|
||||
<option>Bell</option>
|
||||
</Form.Select>
|
||||
</td>
|
||||
<td>{item.video}</td>
|
||||
<td className="text-center">
|
||||
<Button variant="info" size="sm" className="me-2">View</Button>
|
||||
<Button variant="warning" size="sm" className="me-2">Edit</Button>
|
||||
<Button variant="danger" size="sm">Hapus</Button>
|
||||
</td>
|
||||
</>
|
||||
) : settingsView ? (
|
||||
<>
|
||||
<td>{item.service}</td>
|
||||
<td>{item.serviceId}</td>
|
||||
<td>{item.active ? "Aktif" : "Nonaktif"}</td>
|
||||
<td className="text-center">
|
||||
<Button variant={item.active ? "danger" : "success"} size="sm" onClick={() => toggleServiceStatus(item.id)}>
|
||||
{item.active ? "Nonaktifkan" : "Aktifkan"}
|
||||
</Button>
|
||||
</td>
|
||||
</>
|
||||
) : reportView ? (
|
||||
<>
|
||||
<td>{item.name}</td>
|
||||
<td>{item.phone}</td>
|
||||
<td>{item.service}</td>
|
||||
<td>{item.locket}</td>
|
||||
<td>{item.startAt}</td>
|
||||
<td>{item.completedAt}</td>
|
||||
<td className="text-center">
|
||||
<Button variant="warning" size="sm" className="me-2" onClick={() => handleEdit(item.id)}>Edit</Button>
|
||||
<Button variant="danger" size="sm" onClick={() => handleDelete(item.id)}>Hapus</Button>
|
||||
</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td>{item.name}</td>
|
||||
<td>{item.service}</td>
|
||||
<td>{item.serviceId}</td>
|
||||
<td>{item.queueNumber}</td>
|
||||
<td>{item.status === "Dilayani" ? item.locket : "-"}</td>
|
||||
<td>{item.status}</td>
|
||||
{showActions && (
|
||||
<td className="text-center">
|
||||
<Button variant={item.called ? "warning" : "primary"} size="sm" className="me-2" onClick={() => handleCall(item.id)}>
|
||||
{item.called ? "Panggil Ulang" : "Panggil"}
|
||||
</Button>
|
||||
<Button variant="danger" size="sm">Hapus</Button>
|
||||
</td>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Pagination className="justify-content-center">
|
||||
<Pagination.Prev onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} />
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<Pagination.Item key={index + 1} active={index + 1 === currentPage} onClick={() => setCurrentPage(index + 1)}>
|
||||
{index + 1}
|
||||
</Pagination.Item>
|
||||
))}
|
||||
<Pagination.Next onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))} disabled={currentPage === totalPages} />
|
||||
</Pagination>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueueTable;
|
||||
@ -1,35 +1,65 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Container, Row, Col, Card } from "react-bootstrap";
|
||||
import ReactPlayer from "react-player";
|
||||
|
||||
const sampleTickets = [
|
||||
{ number: "A1", counter: "1" },
|
||||
{ number: "A2", counter: "2" },
|
||||
{ number: "A3", counter: "3" },
|
||||
{ number: "A4", counter: "4" },
|
||||
{ number: "A5", counter: "5" },
|
||||
{ number: "A6", counter: "6" },
|
||||
{ number: "A7", counter: "7" },
|
||||
{ number: "A8", counter: "8" },
|
||||
{ number: "A9", counter: "9" },
|
||||
{ number: "A10", counter: "10" },
|
||||
];
|
||||
import { getQueueDisplayData } from "../Admin/QueueActions";
|
||||
|
||||
const QueueDisplay = () => {
|
||||
const [queueData, setQueueData] = useState([]);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setQueueData(getQueueDisplayData());
|
||||
}, []);
|
||||
|
||||
// Pindah data setiap 5 detik
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setStartIndex((prevIndex) => (prevIndex + 1) % queueData.length);
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [queueData]);
|
||||
|
||||
// Data antrian yang akan ditampilkan (5 antrian dari total 25)
|
||||
const currentTicket = queueData[startIndex] || { number: "-", counter: "-" };
|
||||
|
||||
return (
|
||||
<Container fluid className="p-3" style={{ overflowX: "hidden" }}>
|
||||
<Row className="mb-3 g-0">
|
||||
{/* Informasi Bisnis & Nomor Antrian Utama */}
|
||||
<Col md={4} className="px-3 d-flex flex-column justify-content-between">
|
||||
<div className="mb-3 p-3 text-white bg-dark text-center" style={{ borderRadius: "10px" }}>
|
||||
<h4 className="mb-1">Jagowebdev.com</h4>
|
||||
<p className="mb-1">Jl. Zebra III No. 32, Pedurungan Kidul, Semarang</p>
|
||||
<p>Telp: 08561363962</p>
|
||||
<div
|
||||
className="mb-3 p-3 text-white bg-dark d-flex justify-content-between align-items-center position-relative"
|
||||
style={{ borderRadius: "10px" }}
|
||||
>
|
||||
<div className="flex-grow-1">
|
||||
<h3>Pandawa24Jam</h3>
|
||||
<p>Call Center: 081234567891</p>
|
||||
</div>
|
||||
<div className="position-absolute top-50 end-0 translate-middle-y me-3">
|
||||
<h2 className="mb-0">{currentTime.toLocaleTimeString()}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="flex-grow-1 d-flex align-items-center justify-content-center text-center"
|
||||
style={{ borderRadius: "10px", padding: "20px", width: "100%", minHeight: "250px" }}>
|
||||
style={{ borderRadius: "10px", padding: "30px", width: "100%", minHeight: "250px" }}>
|
||||
<Card.Body>
|
||||
<h5 className="mb-2"><strong>NOMOR ANTRIAN</strong></h5>
|
||||
<h1 style={{ fontSize: "80px", fontWeight: "bold", margin: "10px 0" }}>A0</h1>
|
||||
<p style={{ fontSize: "50px", fontWeight: "bold", margin: "10px 0" }}>
|
||||
{currentTicket.number}
|
||||
</p>
|
||||
<h5 className="mt-2">
|
||||
<strong>Menuju Loket: {currentTicket.counter}</strong>
|
||||
</h5>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
@ -50,20 +80,23 @@ const QueueDisplay = () => {
|
||||
</Row>
|
||||
|
||||
{/* Loket Antrian */}
|
||||
<Row className="text-center d-flex flex-wrap justify-content-center row-cols-2 row-cols-md-3 row-cols-lg-5 g-0 px-2">
|
||||
{sampleTickets.map((ticket, index) => (
|
||||
<Col key={index} className="d-flex justify-content-center p-2">
|
||||
<Card className="d-flex align-items-center justify-content-center text-center flex-grow-1"
|
||||
style={{ borderRadius: "10px", width: "100%", minHeight: "150px" }}>
|
||||
<Card.Body>
|
||||
<Card.Title>Nomor Antrian</Card.Title>
|
||||
<h2 style={{ fontSize: "40px", fontWeight: "bold" }}>{ticket.number}</h2>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
<Row className="text-center d-flex flex-wrap justify-content-center g-0">
|
||||
<Col className="d-flex justify-content-center p-2">
|
||||
<Card className="d-flex align-items-center justify-content-center text-center flex-grow-1"
|
||||
style={{ borderRadius: "10px", width: "100%", minHeight: "150px" }}>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<Row>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<h2 style={{ fontSize: "40px", fontWeight: "bold" }}></h2>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,135 +1,162 @@
|
||||
import { Container, Card, Button, Row, Col, Form, } from 'react-bootstrap';
|
||||
import { Container, Card, Button, Row, Col, Form, Carousel } from 'react-bootstrap';
|
||||
import { useState } from 'react';
|
||||
import ReactPlayer from 'react-player';
|
||||
import { FaPrint, FaPalette, FaFileAlt, FaUndo, FaTruck, FaUser } from 'react-icons/fa';
|
||||
|
||||
const services = [
|
||||
{id: "TSP1", name: "test1"},
|
||||
{id: "TSP2", name: "test2"},
|
||||
{id: "TSP3", name: "test3"},
|
||||
{id: "TSP4", name: "test4"},
|
||||
{id: "TSP5", name: "test5"},
|
||||
{id: "TSP6", name: "test6"}
|
||||
{ id: "j0001", name: "Siap Print", icon: <FaPrint size={50} /> },
|
||||
{ id: "j0002", name: "Design/Edit/Kreatif", icon: <FaPalette size={50} /> },
|
||||
{ id: "j0003", name: "FotoCopy/Jilid/Scan", icon: <FaFileAlt size={50} /> },
|
||||
{ id: "j0004", name: "Retur Penjualan", icon: <FaUndo size={50} /> },
|
||||
{ id: "j0005", name: "Online Pickup", icon: <FaTruck size={50} /> },
|
||||
{ id: "j0006", name: "Tamu", icon: <FaUser size={50} /> }
|
||||
];
|
||||
|
||||
const ServiceSelection = () => {
|
||||
|
||||
const [selectedService, setSelectedService] = useState (null);
|
||||
const [selectedService, setSelectedService] = useState(null);
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [ticket, setTicket] = useState (null);
|
||||
const [ticket, setTicket] = useState(null);
|
||||
const [enableForm, setEnableForm] = useState(false);
|
||||
|
||||
const handleServiceSelect = (serviceId) => {
|
||||
setSelectedService(services.find(service => service.id === serviceId));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (selectedService && name && phone) {
|
||||
if (selectedService) {
|
||||
const queueNumber = `${selectedService.id}-${Math.floor(1000 + Math.random() * 9000)}`;
|
||||
const newTicket = {
|
||||
number : queueNumber,
|
||||
serviceId : selectedService.id,
|
||||
service : selectedService.name,
|
||||
name,
|
||||
phone
|
||||
number: queueNumber,
|
||||
serviceId: selectedService.id,
|
||||
service: selectedService.name,
|
||||
name: enableForm ? name : "-",
|
||||
phone: enableForm ? phone : "-",
|
||||
};
|
||||
setTicket(newTicket);
|
||||
setTimeout(() => window.print(), 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{!ticket ? (
|
||||
<Row className='vh-100'>
|
||||
<Col md={6} className='d-flex flex-column'>
|
||||
<h3 className='my-4'>Pilih Layanan</h3>
|
||||
<Row className='flex-grow-1'>
|
||||
<Container>
|
||||
{!ticket ? (
|
||||
<Row className="vh-100">
|
||||
<Col md={6} className="d-flex flex-column">
|
||||
<h3 className="my-4">Pilih Layanan</h3>
|
||||
<Row className="flex-grow-1">
|
||||
{services.map(service => {
|
||||
const isSelected = selectedService?.id === service.id;
|
||||
return (
|
||||
<Col key={service.id} md={6} className='mb-3'>
|
||||
<Card className={`h-100 ${isSelected ? "bg-primary text-white border-primary shadow-sm" : "bg-light"}`}>
|
||||
<Card.Body className='d-flex flex-column justify-content-between'>
|
||||
<Card.Title className='text-center'>{service.name}</Card.Title>
|
||||
<Button
|
||||
variant={isSelected ? "light" : "outline-primary"}
|
||||
onClick={() => handleServiceSelect(service.id)}>
|
||||
{isSelected ? "Dipilih" : "Pilih Layanan"}
|
||||
</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
)
|
||||
<Col key={service.id} md={6} className="mb-3">
|
||||
<Card
|
||||
className={`h-100 ${isSelected ? "bg-primary text-white border-primary shadow-sm" : "bg-light"}`}
|
||||
onClick={() => {
|
||||
handleServiceSelect(service.id);
|
||||
if (!enableForm) {
|
||||
handleSubmit(new Event("submit"));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Card.Body className="d-flex flex-column justify-content-center align-items-center">
|
||||
{service.icon}
|
||||
<Card.Text className="text-center mt-5">
|
||||
<h5>{service.name}</h5>
|
||||
</Card.Text>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col md={6} className='d-flex flex-column'>
|
||||
<h3 className='my-4'>Masukan Data Diri</h3>
|
||||
<Card className='flex-grow-1'>
|
||||
<Card.Body className='d-flex flex-column'>
|
||||
<Form onSubmit={handleSubmit} className='flex-grow-1 d-flex flex-column'>
|
||||
<Form.Group className='mb-3' controlId='formName'>
|
||||
<Form.Label>Nama</Form.Label>
|
||||
<Form.Control
|
||||
type='text'
|
||||
placeholder='@example: pandawa'
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className='mb-3' controlId='formPhone'>
|
||||
<Form.Label>No Telepon</Form.Label>
|
||||
<Form.Control
|
||||
type='text'
|
||||
placeholder='@example: 08xxxxxxxxxxx'
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant='primary' type='submit'>Submit</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<div className='d-flex flex-grow-1 my-3'>
|
||||
<ReactPlayer
|
||||
url="https://www.youtube.com/watch?v=FaU8BkqmXzo&pp=ygULc3RvY2sgdmlkZW8%3D"
|
||||
controls
|
||||
width="100%"
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col md={6} className="d-flex flex-column">
|
||||
<div className="d-flex align-items-center my-4">
|
||||
<h3 className="mb-0 me-3">Data Diri</h3>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
onChange={() => setEnableForm(!enableForm)}
|
||||
style={{ transform: "scale(1.5)" }}
|
||||
/>
|
||||
<p className="my-2 text-muted" style={{ transform: "scale(0.9)" }}>
|
||||
Beri tanda centang untuk mengisi form
|
||||
</p>
|
||||
</div>
|
||||
<Card className={`flex-grow-1 ${enableForm ? "" : "bg-secondary bg-opacity-10"}`}>
|
||||
<Card.Body className="d-flex flex-column">
|
||||
<Form onSubmit={handleSubmit} className="flex-grow-1 d-flex flex-column">
|
||||
<Form.Group className="mb-3" controlId="formName">
|
||||
<Form.Label className={enableForm ? "" : "text-muted"}>Nama</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: pandawa"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={`border ${enableForm ? "border-primary" : "border-secondary"} ${enableForm ? "" : "bg-light text-secondary"}`}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formPhone">
|
||||
<Form.Label className={enableForm ? "" : "text-muted"}>No Telepon</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: 08xxxxxxxxxxx"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={`border ${enableForm ? "border-primary" : "border-secondary"} ${enableForm ? "" : "bg-light text-secondary"}`}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant={enableForm ? "primary" : "secondary"} type="submit" disabled={!enableForm}>
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<div className="d-flex flex-grow-1 my-3 align-items-center justify-content-center">
|
||||
<Carousel>
|
||||
<Carousel.Item interval={3000}>
|
||||
<img className="d-block w-100" src="https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=800&h=400&fit=crop" alt="First slide" height={400} />
|
||||
<Carousel.Caption>
|
||||
<h3>First Slide Label</h3>
|
||||
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
|
||||
</Carousel.Caption>
|
||||
</Carousel.Item>
|
||||
<Carousel.Item interval={3000}>
|
||||
<img className="d-block w-100" src="https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=800&h=400&fit=crop" alt="Second slide" height={400} />
|
||||
<Carousel.Caption>
|
||||
<h3>Second Slide Label</h3>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
||||
</Carousel.Caption>
|
||||
</Carousel.Item>
|
||||
<Carousel.Item interval={3000}>
|
||||
<img className="d-block w-100" src="https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=800&h=400&fit=crop" alt="Third slide" height={400} />
|
||||
<Carousel.Caption>
|
||||
<h3>Third Slide Label</h3>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>
|
||||
</Carousel.Caption>
|
||||
</Carousel.Item>
|
||||
</Carousel>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Container className="d-flex justify-content-center align-items-center vh-100">
|
||||
<Card className="p-4 text-center" style={{ maxWidth: "400px", width: "100%" }}>
|
||||
<Card.Body>
|
||||
<Card.Title className="fw-bold fs-3">Tiket Antrian</Card.Title>
|
||||
<hr />
|
||||
<Card.Text className="fw-bold text-uppercase fs-2 bg-light p-3 rounded">
|
||||
{ticket.number}
|
||||
</Card.Text>
|
||||
<Card.Text>
|
||||
<strong>Layanan :</strong> {ticket.service}
|
||||
</Card.Text>
|
||||
<Card.Text>
|
||||
<strong>Nama :</strong> {ticket.name}
|
||||
</Card.Text>
|
||||
<Card.Text>
|
||||
<strong>No Telepon :</strong> {ticket.phone}
|
||||
</Card.Text>
|
||||
<Button variant="success" onClick={() => window.print()}>
|
||||
Cetak Tiket
|
||||
</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
) : (
|
||||
<Container className="d-flex justify-content-center align-items-center vh-100">
|
||||
<Card className="p-4 text-center" style={{ maxWidth: "400px", width: "100%" }}>
|
||||
<Card.Body>
|
||||
<Card.Title className="fw-bold fs-3">Tiket Antrian</Card.Title>
|
||||
<hr />
|
||||
<Card.Text className="fw-bold text-uppercase fs-2 bg-light p-3 rounded">{ticket.number}</Card.Text>
|
||||
<Card.Text><strong>Layanan :</strong> {ticket.service}</Card.Text>
|
||||
<Card.Text><strong>Nama :</strong> {ticket.name}</Card.Text>
|
||||
<Card.Text><strong>No Telepon :</strong> {ticket.phone}</Card.Text>
|
||||
<Button variant="success" onClick={() => window.print()}>Cetak Tiket</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceSelection
|
||||
export default ServiceSelection;
|
||||
|
||||
21
src/layouts/AdminLayout.jsx
Normal file
21
src/layouts/AdminLayout.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import Sidebar from "../components/Admin/AdminSidebar";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import AdminNavbar from "../components/Admin/AdminNavbar";
|
||||
import AdminFooter from "../components/Admin/AdminFooter";
|
||||
|
||||
const AdminLayout = () => {
|
||||
return (
|
||||
<div className="d-flex flex-column vh-100">
|
||||
<AdminNavbar />
|
||||
<div className="d-flex flex-grow-1">
|
||||
<Sidebar />
|
||||
<div className="flex-grow-1 p-4 bg-light">
|
||||
<Outlet /> {/* Ini akan menampilkan halaman konten admin */}
|
||||
</div>
|
||||
</div>
|
||||
<AdminFooter />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminLayout;
|
||||
12
src/pages/Admin/CallQueuePage.jsx
Normal file
12
src/pages/Admin/CallQueuePage.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable";
|
||||
|
||||
const CallQueuePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Panggil Atrian Page</h2>
|
||||
<QueueTable callQueueView={true}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallQueuePage
|
||||
13
src/pages/Admin/QueueListPage.jsx
Normal file
13
src/pages/Admin/QueueListPage.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable";
|
||||
|
||||
const QueueListPage = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Daftar Antrian</h2>
|
||||
<QueueTable showActions={false}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueueListPage;
|
||||
12
src/pages/Admin/QueueReportPage.jsx
Normal file
12
src/pages/Admin/QueueReportPage.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable"
|
||||
|
||||
const QueueReportPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Report Antrian Page</h2>
|
||||
<QueueTable reportView={true}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueReportPage
|
||||
12
src/pages/Admin/QueueSettingsDisplayPage.jsx
Normal file
12
src/pages/Admin/QueueSettingsDisplayPage.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable"
|
||||
|
||||
const QueueSettingsDisplayPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Setting Display Antrian Page</h2>
|
||||
<QueueTable displayView={true}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueSettingsDisplayPage
|
||||
12
src/pages/Admin/QueueSettingsMenuPage.jsx
Normal file
12
src/pages/Admin/QueueSettingsMenuPage.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable"
|
||||
|
||||
const QueueSettingsMenuPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Settings Menu Layanan Page</h2>
|
||||
<QueueTable settingsView={true}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueSettingsMenuPage
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
const QueueSettingsDisplayPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueSettingsDisplayPage
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
const QueueSettingsMenuPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueSettingsMenuPage
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
const CallQueuePage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallQueuePage
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
const QueueListPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueListPage
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
const QueueReportPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueueReportPage
|
||||
@ -1,29 +1,35 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
|
||||
import QueueDisplayPage from "../pages/Display/QueueDisplayPage"
|
||||
import QueueSettingsDisplayPage from "../pages/Display/QueueSettingsDisplayPage"
|
||||
import QueueSettingsMenuPage from "../pages/Display/QueueSettingsMenuPage"
|
||||
import QueueMenuPage from "../pages/Display/QueueMenuPage"
|
||||
import CallQueuePage from "../pages/Queue/CallQueuePage"
|
||||
import QueueListPage from "../pages/Queue/QueueListPage"
|
||||
import QueueReportPage from "../pages/Queue/QueueReportPage"
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
|
||||
import AdminLayout from "../layouts/AdminLayout";
|
||||
import QueueListPage from "../pages/admin/QueueListPage";
|
||||
import CallQueuePage from "../pages/admin/CallQueuePage";
|
||||
import QueueReportPage from "../pages/admin/QueueReportPage";
|
||||
import QueueSettingsDisplayPage from "../pages/admin/QueueSettingsDisplayPage";
|
||||
import QueueSettingsMenuPage from "../pages/admin/QueueSettingsMenuPage";
|
||||
import QueueDisplayPage from "../pages/display/QueueDisplayPage";
|
||||
import QueueMenuPage from "../pages/display/QueueMenuPage";
|
||||
|
||||
const AppRoutes = () => {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* Queue Pages */}
|
||||
<Route path="/" element={<QueueListPage/>}/>
|
||||
<Route path="/queue-list" element={<QueueListPage/>}/>
|
||||
<Route path="/call-queue" element={<CallQueuePage/>}/>
|
||||
<Route path="/queue-report" element={<QueueReportPage/>}/>
|
||||
{/* Display Pages */}
|
||||
<Route path="/queue-menu-settings" element={<QueueSettingsMenuPage/>}/>
|
||||
<Route path="/queue-menu" element={<QueueMenuPage/>}/>
|
||||
<Route path="/queue-display-settings" element={<QueueSettingsDisplayPage/>}/>
|
||||
<Route path="/queue-display" element={<QueueDisplayPage/>}/>
|
||||
{/* Redirect default ke queue-list */}
|
||||
<Route path="/admin" element={<Navigate to="/admin/queue-list" replace />} />
|
||||
|
||||
{/* Rute untuk Admin */}
|
||||
<Route path="/admin" element={<AdminLayout />}>
|
||||
<Route path="queue-list" element={<QueueListPage />} />
|
||||
<Route path="call-queue" element={<CallQueuePage />} />
|
||||
<Route path="queue-report" element={<QueueReportPage />} />
|
||||
<Route path="queue-settings-display" element={<QueueSettingsDisplayPage />} />
|
||||
<Route path="queue-settings-menu" element={<QueueSettingsMenuPage />} />
|
||||
</Route>
|
||||
|
||||
{/* Rute untuk Customer */}
|
||||
<Route path="/queue-display" element={<QueueDisplayPage />} />
|
||||
<Route path="/queue-menu" element={<QueueMenuPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRoutes
|
||||
export default AppRoutes;
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
:root {
|
||||
--bs-body-font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
/* opacity: 0; */
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.mb-3.p-3.text-white.bg-dark:hover .close-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
25
txt
Normal file
25
txt
Normal file
@ -0,0 +1,25 @@
|
||||
{ id: 1, name: "John Doe", phone: "08123456789", service: "Konsultasi", serviceId: "KS", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default", video: "Video1.mp4" },
|
||||
{ id: 2, name: "Jane Smith", phone: "08234567890", service: "Pendaftaran", serviceId: "PF", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep", video: "Video2.mp4" },
|
||||
{ id: 3, name: "Michael Johnson", phone: "08345678901", service: "Pembayaran", serviceId: "PY", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell", video: "Video3.mp4" },
|
||||
{ id: 4, name: "Emily Brown", phone: "08456789012", service: "Konsultasi", serviceId: "KS", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime", video: "Video4.mp4" },
|
||||
{ id: 5, name: "David Wilson", phone: "08567890123", service: "Pendaftaran", serviceId: "PF", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding", video: "Video5.mp4" },
|
||||
{ id: 6, name: "Sophia Martinez", phone: "08678901234", service: "Pembayaran", serviceId: "PY", locket: "06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Selesai", called: false, active: true, audio: "Ping", video: "Video6.mp4" },
|
||||
{ id: 7, name: "James Anderson", phone: "08789012345", service: "Konsultasi", serviceId: "KS", locket: "03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true, audio: "Chirp", video: "Video7.mp4" },
|
||||
{ id: 8, name: "Olivia Thomas", phone: "08890123456", service: "Pendaftaran", serviceId: "PF", locket: "07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Dilayani", called: false, active: true, audio: "Ring", video: "Video8.mp4" },
|
||||
{ id: 9, name: "Liam White", phone: "08901234567", service: "Pembayaran", serviceId: "PY", locket: "09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Selesai", called: false, active: true, audio: "Tone", video: "Video9.mp4" },
|
||||
{ id: 10, name: "Emma Harris", phone: "08012345678", service: "Konsultasi", serviceId: "KS", locket: "10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true, audio: "Alarm", video: "Video10.mp4" },
|
||||
{ id: 11, name: "John Doe", phone: "08123456789", service: "Konsultasi", serviceId: "KS", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default", video: "Video1.mp4" },
|
||||
{ id: 12, name: "Jane Smith", phone: "08234567890", service: "Pendaftaran", serviceId: "PF", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep", video: "Video2.mp4" },
|
||||
{ id: 13, name: "Michael Johnson", phone: "08345678901", service: "Pembayaran", serviceId: "PY", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell", video: "Video3.mp4" },
|
||||
{ id: 14, name: "Emily Brown", phone: "08456789012", service: "Konsultasi", serviceId: "KS", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime", video: "Video4.mp4" },
|
||||
{ id: 15, name: "David Wilson", phone: "08567890123", service: "Pendaftaran", serviceId: "PF", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding", video: "Video5.mp4" },
|
||||
{ id: 16, name: "John Doe", phone: "08123456789", service: "Konsultasi", serviceId: "KS", locket: "01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true, audio: "Default", video: "Video1.mp4" },
|
||||
{ id: 17, name: "Jane Smith", phone: "08234567890", service: "Pendaftaran", serviceId: "PF", locket: "04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Dilayani", called: false, active: true, audio: "Beep", video: "Video2.mp4" },
|
||||
{ id: 18, name: "Michael Johnson", phone: "08345678901", service: "Pembayaran", serviceId: "PY", locket: "08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Selesai", called: false, active: true, audio: "Bell", video: "Video3.mp4" },
|
||||
{ id: 19, name: "Emily Brown", phone: "08456789012", service: "Konsultasi", serviceId: "KS", locket: "02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true, audio: "Chime", video: "Video4.mp4" },
|
||||
{ id: 20, name: "David Wilson", phone: "08567890123", service: "Pendaftaran", serviceId: "PF", locket: "05", startAt: "12:50 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Dilayani", called: false, active: true, audio: "Ding", video: "Video5.mp4" },
|
||||
{ id: 21, name: "Sophia Martinez", phone: "08678901234", service: "Pembayaran", serviceId: "PY", locket: "06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Selesai", called: false, active: true, audio: "Ping", video: "Video6.mp4" },
|
||||
{ id: 22, name: "James Anderson", phone: "08789012345", service: "Konsultasi", serviceId: "KS", locket: "03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true, audio: "Chirp", video: "Video7.mp4" },
|
||||
{ id: 23, name: "Olivia Thomas", phone: "08890123456", service: "Pendaftaran", serviceId: "PF", locket: "07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Dilayani", called: false, active: true, audio: "Ring", video: "Video8.mp4" },
|
||||
{ id: 24, name: "Liam White", phone: "08901234567", service: "Pembayaran", serviceId: "PY", locket: "09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Selesai", called: false, active: true, audio: "Tone", video: "Video9.mp4" },
|
||||
{ id: 25, name: "Emma Harris", phone: "08012345678", service: "Konsultasi", serviceId: "KS", locket: "10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true, audio: "Alarm", video: "Video10.mp4" },
|
||||
Loading…
Reference in New Issue
Block a user