26/02/2025

This commit is contained in:
damarrsyh 2025-02-26 16:47:02 +07:00
parent 11d418602a
commit 492552d373
27 changed files with 751 additions and 210 deletions

View File

@ -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
View File

@ -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",

View File

@ -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
View 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
View 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

View 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">&copy; {new Date().getFullYear()} Admin Dashboard</p>
</Container>
</footer>
);
};
export default AdminFooter;

View 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;

View 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;

View 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
};
};

View 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;

View File

@ -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>
);
};

View File

@ -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;

View 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;

View 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

View 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;

View 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

View 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

View 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

View File

@ -1,10 +0,0 @@
const QueueSettingsDisplayPage = () => {
return (
<div>
</div>
)
}
export default QueueSettingsDisplayPage

View File

@ -1,10 +0,0 @@
const QueueSettingsMenuPage = () => {
return (
<div>
</div>
)
}
export default QueueSettingsMenuPage

View File

@ -1,10 +0,0 @@
const CallQueuePage = () => {
return (
<div>
</div>
)
}
export default CallQueuePage

View File

@ -1,10 +0,0 @@
const QueueListPage = () => {
return (
<div>
</div>
)
}
export default QueueListPage

View File

@ -1,10 +0,0 @@
const QueueReportPage = () => {
return (
<div>
</div>
)
}
export default QueueReportPage

View File

@ -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;

View File

@ -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
View 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" },