Update 28/02/2025
This commit is contained in:
parent
c31bd10c5c
commit
a0e301b401
406
db.json
406
db.json
@ -4,6 +4,7 @@
|
||||
"id": "O001",
|
||||
"name": "Rina Wijaya",
|
||||
"email": "rina@example.com",
|
||||
"counters": "loket 1",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12345",
|
||||
@ -15,10 +16,12 @@
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "In Progress",
|
||||
"created_at": "2025-02-27T08:30:00Z"
|
||||
"created_at": "2025-02-27T08:30:00Z",
|
||||
"time_start": "2025-02-27T08:45:00Z",
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12346",
|
||||
@ -30,10 +33,63 @@
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T09:00:00Z"
|
||||
"created_at": "2025-02-27T09:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12351",
|
||||
"customer": {
|
||||
"id": "C007",
|
||||
"name": "Eka Prasetyo",
|
||||
"phone": "+6285544332211",
|
||||
"email": "eka@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T09:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12352",
|
||||
"customer": {
|
||||
"id": "C008",
|
||||
"name": "Rahmat Hidayat",
|
||||
"phone": "+6287766554433",
|
||||
"email": "rahmat@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T10:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12353",
|
||||
"customer": {
|
||||
"id": "C009",
|
||||
"name": "Lestari Wijaya",
|
||||
"phone": "+6289988776655",
|
||||
"email": "lestari@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T10:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -41,6 +97,7 @@
|
||||
"id": "O002",
|
||||
"name": "Doni Saputra",
|
||||
"email": "doni@example.com",
|
||||
"counters": "loket 2",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12347",
|
||||
@ -52,10 +109,12 @@
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "design"
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Completed",
|
||||
"created_at": "2025-02-27T09:30:00Z"
|
||||
"created_at": "2025-02-27T09:30:00Z",
|
||||
"time_start": "2025-02-27T09:45:00Z",
|
||||
"time_end": "2025-02-27T10:15:00Z"
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12348",
|
||||
@ -67,47 +126,350 @@
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "fotocopy"
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "In Progress",
|
||||
"created_at": "2025-02-27T10:00:00Z"
|
||||
"created_at": "2025-02-27T10:00:00Z",
|
||||
"time_start": "2025-02-27T10:20:00Z",
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12354",
|
||||
"customer": {
|
||||
"id": "C010",
|
||||
"name": "Rina Astuti",
|
||||
"phone": "+6286677889900",
|
||||
"email": "rina.astuti@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T11:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12355",
|
||||
"customer": {
|
||||
"id": "C011",
|
||||
"name": "Indra Wijaya",
|
||||
"phone": "+6287766554433",
|
||||
"email": "indra@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T11:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O003",
|
||||
"name": "Sari Ningsih",
|
||||
"name": "Sari Dewi",
|
||||
"email": "sari@example.com",
|
||||
"counters": "loket 3",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12349",
|
||||
"customer": {
|
||||
"id": "j000",
|
||||
"name": "Rudi Hartono",
|
||||
"phone": "+6287711223344",
|
||||
"email": "rudi@example.com"
|
||||
"id": "C005",
|
||||
"name": "Indra Wijaya",
|
||||
"phone": "+6286677889900",
|
||||
"email": "indra@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "design"
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T10:30:00Z"
|
||||
"created_at": "2025-02-27T10:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12350",
|
||||
"customer": {
|
||||
"id": "C006",
|
||||
"name": "Dewi Anggraini",
|
||||
"phone": "+6286677889900",
|
||||
"email": "dewi@example.com"
|
||||
"name": "Rina Astuti",
|
||||
"phone": "+6287766554433",
|
||||
"email": "rina.astuti@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T11:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12356",
|
||||
"customer": {
|
||||
"id": "C012",
|
||||
"name": "Eka Prasetyo",
|
||||
"phone": "+6285544332211",
|
||||
"email": "eka@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T11:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12357",
|
||||
"customer": {
|
||||
"id": "C013",
|
||||
"name": "Rahmat Hidayat",
|
||||
"phone": "+6287766554433",
|
||||
"email": "rahmat@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T12:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12358",
|
||||
"customer": {
|
||||
"id": "C014",
|
||||
"name": "Lestari Wijaya",
|
||||
"phone": "+6289988776655",
|
||||
"email": "lestari@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Completed",
|
||||
"created_at": "2025-02-27T11:00:00Z"
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T12:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O004",
|
||||
"name": "Rina Wijaya",
|
||||
"email": "rina@example.com",
|
||||
"counters": "loket 4",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12359",
|
||||
"customer": {
|
||||
"id": "C015",
|
||||
"name": "Budi Santoso",
|
||||
"phone": "+628123456789",
|
||||
"email": "budi@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T13:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12360",
|
||||
"customer": {
|
||||
"id": "C016",
|
||||
"name": "Siti Aminah",
|
||||
"phone": "+628987654321",
|
||||
"email": "siti@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T13:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12361",
|
||||
"customer": {
|
||||
"id": "C017",
|
||||
"name": "Eka Prasetyo",
|
||||
"phone": "+6285544332211",
|
||||
"email": "eka@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T14:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12362",
|
||||
"customer": {
|
||||
"id": "C018",
|
||||
"name": "Rahmat Hidayat",
|
||||
"phone": "+6287766554433",
|
||||
"email": "rahmat@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T14:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12363",
|
||||
"customer": {
|
||||
"id": "C019",
|
||||
"name": "Lestari Wijaya",
|
||||
"phone": "+6289988776655",
|
||||
"email": "lestari@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T15:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O005",
|
||||
"name": "Doni Saputra",
|
||||
"email": "doni@example.com",
|
||||
"counters": "loket 5",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12364",
|
||||
"customer": {
|
||||
"id": "C020",
|
||||
"name": "Ahmad Fauzi",
|
||||
"phone": "+6285566778899",
|
||||
"email": "ahmad@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T15:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12365",
|
||||
"customer": {
|
||||
"id": "C021",
|
||||
"name": "Lina Kusuma",
|
||||
"phone": "+6289988776655",
|
||||
"email": "lina@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T16:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12366",
|
||||
"customer": {
|
||||
"id": "C022",
|
||||
"name": "Rina Astuti",
|
||||
"phone": "+6286677889900",
|
||||
"email": "rina.astuti@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T16:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12367",
|
||||
"customer": {
|
||||
"id": "C023",
|
||||
"name": "Indra Wijaya",
|
||||
"phone": "+6287766554433",
|
||||
"email": "indra@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "Print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T17:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12368",
|
||||
"customer": {
|
||||
"id": "C024",
|
||||
"name": "Eka Prasetyo",
|
||||
"phone": "+6285544332211",
|
||||
"email": "eka@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "Fotocopy"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T17:30:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O006",
|
||||
"name": "Sari Dewi",
|
||||
"email": "sari@example.com",
|
||||
"counters": "loket 6",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12369",
|
||||
"customer": {
|
||||
"id": "C025",
|
||||
"name": "Rahmat Hidayat",
|
||||
"phone": "+6287766554433",
|
||||
"email": "rahmat@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "Design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T18:00:00Z",
|
||||
"time_start": null,
|
||||
"time_end": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
public/Logo.jpg
Normal file
BIN
public/Logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
BIN
public/profile.jpg
Normal file
BIN
public/profile.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 744 KiB |
@ -2,7 +2,7 @@ import { Container } from "react-bootstrap";
|
||||
|
||||
const AdminFooter = () => {
|
||||
return (
|
||||
<footer className="bg-light text-center py-3 mt-auto shadow-sm" style={{ borderTop: "1px solid #D3D3D3" }}>
|
||||
<footer className="bg-light text-center py-2 mt-auto shadow-sm" style={{ borderTop: "1px solid #D3D3D3" }}>
|
||||
<Container>
|
||||
<p className="mb-0">© {new Date().getFullYear()} Admin Dashboard</p>
|
||||
</Container>
|
||||
|
||||
@ -1,13 +1,42 @@
|
||||
import { Navbar, Container } from "react-bootstrap";
|
||||
import { Navbar, Container, Nav, Dropdown, Button, Image } from "react-bootstrap";
|
||||
import { FaSun, FaMoon, FaUsers } from "react-icons/fa";
|
||||
import useTheme from "../Shared/useTheme"; // Import hook tema
|
||||
|
||||
const AdminNavbar = () => {
|
||||
const { darkMode, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Navbar bg="light" variant="light" expand="lg" className="shadow-sm">
|
||||
<Container>
|
||||
<Navbar.Brand href="/admin">Admin Dashboard</Navbar.Brand>
|
||||
<Navbar bg={darkMode ? "dark" : "light"} variant={darkMode ? "dark" : "light"} expand="lg" className="shadow-sm">
|
||||
<Container fluid className="mx-4">
|
||||
{/* Logo & Brand */}
|
||||
<Navbar.Brand href="/" className="d-flex align-items-center">
|
||||
<FaUsers className="me-2"/>
|
||||
<span className="fw-bold">Antrian</span>
|
||||
</Navbar.Brand>
|
||||
|
||||
{/* Profil & Theme Toggle */}
|
||||
<Nav className="ms-auto d-flex align-items-center">
|
||||
{/* Toggle Dark/Light Mode */}
|
||||
<Button variant={darkMode ? "bg-dark text-white" : "bg-light text-dark"} onClick={toggleTheme} className="rounded-circle">
|
||||
{darkMode ? <FaSun size={15} /> : <FaMoon size={15} />}
|
||||
</Button>
|
||||
|
||||
{/* Dropdown Profile */}
|
||||
<Dropdown align="end">
|
||||
<Dropdown.Toggle variant="transparent" id="dropdown-profile" className={`${darkMode ? "text-light" : "text-dark"} mx-2`}>
|
||||
Admin
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href="/settings">Settings</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item href="/logout">Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
<Image src="/profile.jpg" alt="Profile" roundedCircle width="30" height="30"/>
|
||||
</Dropdown>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminNavbar;
|
||||
export default AdminNavbar;
|
||||
|
||||
@ -9,6 +9,7 @@ export const getProcessedQueues = async () => {
|
||||
const processedData = data.map((operator) => ({
|
||||
operatorId: operator.id,
|
||||
operatorName: operator.name,
|
||||
operatorCounter: operator.counters,
|
||||
queues: operator.queues.map((queue) => ({
|
||||
id: queue.queue_id,
|
||||
customerName: queue.customer.name,
|
||||
@ -17,6 +18,7 @@ export const getProcessedQueues = async () => {
|
||||
serviceId: queue.service.id,
|
||||
serviceName: queue.service.name,
|
||||
status: queue.status,
|
||||
operatorCounter: operator.counters,
|
||||
createdAt: queue.created_at,
|
||||
})),
|
||||
}));
|
||||
|
||||
@ -1,59 +1,87 @@
|
||||
|
||||
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getProcessedQueues } from "./TestQueueActions";
|
||||
import { Card, Table, Container, Button, Modal, ListGroup } from "react-bootstrap";
|
||||
import { Card, Table, Container, Button, Modal, ListGroup, Pagination } from "react-bootstrap";
|
||||
|
||||
const TestQueueTable = () => {
|
||||
const [queues, setQueues] = useState([]);
|
||||
const [selectedQueue, setSelectedQueue] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [calledQueues, setCalledQueues] = useState(new Set());
|
||||
|
||||
// Pagination states
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 5; // Jumlah antrian per halaman
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await getProcessedQueues();
|
||||
// Flatten data agar tidak dikelompokkan per operator
|
||||
const allQueues = data.flatMap((operator) =>
|
||||
operator.queues.map((queue) => ({
|
||||
...queue,
|
||||
operatorName: operator.operatorName, // Menyimpan nama operator di setiap antrian
|
||||
operatorName: operator.operatorName,
|
||||
}))
|
||||
);
|
||||
// Sort data: Prioritaskan yang Waiting dan urutkan berdasarkan createdAt
|
||||
|
||||
allQueues.sort((a, b) => {
|
||||
if (a.status === "Waiting" && b.status !== "Waiting") return -1;
|
||||
if (a.status !== "Waiting" && b.status === "Waiting") return 1;
|
||||
return new Date(a.createdAt) - new Date(b.createdAt);
|
||||
});
|
||||
|
||||
setQueues(allQueues);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// Data dummy untuk loket operator yang tersedia
|
||||
const availableCounters = [
|
||||
{ id: "LKT001", name: "Loket 1" },
|
||||
{ id: "LKT002", name: "Loket 2" },
|
||||
{ id: "LKT003", name: "Loket 3" },
|
||||
];
|
||||
|
||||
// Handler untuk membuka modal dan memilih queue
|
||||
const handleCallQueue = (queue) => {
|
||||
setSelectedQueue(queue);
|
||||
setShowModal(true);
|
||||
setCalledQueues((prev) => new Set(prev).add(queue.id)); // Tandai queue sebagai dipanggil
|
||||
};
|
||||
|
||||
// Pagination Logic
|
||||
const indexOfLastItem = currentPage * itemsPerPage;
|
||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||
const currentQueues = queues.slice(indexOfFirstItem, indexOfLastItem);
|
||||
const totalPages = Math.ceil(queues.length / itemsPerPage);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Card className="shadow">
|
||||
<Card.Header className="bg-primary text-white">
|
||||
<Card.Header className="d-flex justify-content-between align-items-center">
|
||||
<h5 className="mb-0">List Antrian</h5>
|
||||
{/* Pagination Controls */}
|
||||
<Pagination className="custom-pagination justify-content-center mb-0">
|
||||
<Pagination.Prev
|
||||
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
|
||||
disabled={currentPage === 1}
|
||||
/>
|
||||
{Array.from({ length: totalPages }, (_, 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>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Table striped bordered hover size="sm" responsive>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Customer</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
@ -64,14 +92,15 @@ const TestQueueTable = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{queues.length > 0 ? (
|
||||
queues.map((queue) => (
|
||||
{currentQueues.length > 0 ? (
|
||||
currentQueues.map((queue, index) => (
|
||||
<tr key={queue.id}>
|
||||
<td>{queue.customerName}</td>
|
||||
<td>{queue.customerEmail}</td>
|
||||
<td>{queue.customerPhone}</td>
|
||||
<td>{queue.serviceName}</td>
|
||||
<td>
|
||||
<td className="">{index+1}</td>
|
||||
<td className="">{queue.customerName}</td>
|
||||
<td className="">{queue.customerEmail}</td>
|
||||
<td className="">{queue.customerPhone}</td>
|
||||
<td className="">{queue.serviceName}</td>
|
||||
<td className="">
|
||||
<span
|
||||
className={`badge ${
|
||||
queue.status === "Completed"
|
||||
@ -84,15 +113,15 @@ const TestQueueTable = () => {
|
||||
{queue.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>{new Date(queue.createdAt).toLocaleString()}</td>
|
||||
<td className="text-center p-2">
|
||||
<td className="">{new Date(queue.createdAt).toLocaleString()}</td>
|
||||
<td className=" text-center">
|
||||
{queue.status !== "In Progress" && queue.status !== "Completed" && (
|
||||
<Button
|
||||
variant="info"
|
||||
variant={calledQueues.has(queue.id) ? "warning" : "info text-white"}
|
||||
size="sm"
|
||||
onClick={() => handleCallQueue(queue)}
|
||||
>
|
||||
Panggil Antrian
|
||||
{calledQueues.has(queue.id) ? "Panggil Ulang" : "Panggil"}
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
@ -100,7 +129,7 @@ const TestQueueTable = () => {
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="9" className="text-center text-muted">
|
||||
<td className=" text-center text-muted" colSpan="7">
|
||||
No queues available
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -6,6 +6,12 @@ import ReactPlayer from "react-player";
|
||||
const TestQueueDisplay = () => {
|
||||
const [queues, setQueues] = useState([]);
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
const [showVideo, setShowVideo] = useState(true);
|
||||
const colors = ["primary", "secondary", "success", "danger", "warning", "info", "dark"];
|
||||
|
||||
const getRandomColor = (index) => {
|
||||
return colors[index % colors.length];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@ -32,16 +38,24 @@ const TestQueueDisplay = () => {
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setShowVideo(!showVideo);
|
||||
}, 180000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [showVideo]);
|
||||
|
||||
return (
|
||||
<Container fluid className="p-3" style={{ overflowX: "hidden" }}>
|
||||
<Container fluid className="p-3" style={{ overflowX: "hidden", maxHeight: "100vh", overflow: "hidden"}}>
|
||||
<Row className="mb-3 g-0">
|
||||
<Col md={4} className="px-3 d-flex flex-column justify-content-between">
|
||||
<Col md={4} className="d-flex flex-column justify-content-between">
|
||||
<div
|
||||
className="shadow mb-3 p-3 d-flex justify-content-between align-items-center position-relative"
|
||||
className="shadow mb-3 pt-3 ps-3 bg-primary text-white d-flex justify-content-between align-items-center position-relative"
|
||||
style={{ borderRadius: "10px" }}
|
||||
>
|
||||
<div className="flex-grow-1">
|
||||
<h5>Pandawa24Jam</h5>
|
||||
<h4>Pandawa24Jam</h4>
|
||||
<p>CS: 081234567891</p>
|
||||
</div>
|
||||
<div className="position-absolute top-50 end-0 translate-middle-y me-3">
|
||||
@ -49,52 +63,98 @@ const TestQueueDisplay = () => {
|
||||
</div>
|
||||
</div>
|
||||
{queues.length > 0 ? (
|
||||
<Card className="shadow flex-grow-1 d-flex align-items-center justify-content-center text-center"
|
||||
style={{ borderRadius: "10px", padding: "30px", width: "100%", minHeight: "250px" }}>
|
||||
<h1 className="display-3 text-primary">{queues[0].id}</h1>
|
||||
<h4>{queues[0].customerName}</h4>
|
||||
<p>{queues[0].serviceName}</p>
|
||||
// Card Antrian Dipanggil
|
||||
<Card className="shadow flex-grow-1 text-center"
|
||||
style={{ borderRadius: "10px", width: "100%", minHeight: "250px" }}>
|
||||
<Card.Header className="bg-info text-white">
|
||||
<h3 className="fw-bold">
|
||||
NOMOR ANTRIAN
|
||||
</h3>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<h1 className="display-3 fw-bold p-3">{queues[0].id}</h1>
|
||||
</Card.Body>
|
||||
<Card.Footer className="bg-info text-white">
|
||||
<h5 className="fw-bold">
|
||||
{queues[0].operatorCounter ? `${queues[0].operatorCounter}` : "Loket Tidak Diketahui"}
|
||||
</h5>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
) : (
|
||||
<p className="text-muted">Tidak ada antrian aktif</p>
|
||||
)}
|
||||
</Col>
|
||||
<Col md={8} className="px-3 d-flex align-items-stretch">
|
||||
<div className="w-100 d-flex">
|
||||
{/* Video */}
|
||||
<Col md={8} className="ps-3 d-flex align-items-stretch">
|
||||
{showVideo ? (
|
||||
<div className="w-100 d-flex transition" style={{ borderRadius: "10px", overflow: "hidden", flexGrow: 1 }}>
|
||||
<ReactPlayer
|
||||
url="https://www.youtube.com/watch?v=FaU8BkqmXzo&pp=ygULc3RvY2sgdmlkZW8%3D"
|
||||
controls
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="react-player"
|
||||
style={{ borderRadius: "10px", overflow: "hidden", flexGrow: 1 }}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="shadow flex-grow-1 transition" style={{ borderRadius: "10px", width: "100%", minHeight: "250px" }}>
|
||||
<Card.Header className="bg-warning text-white">
|
||||
<h3 className="fw-bold">
|
||||
CUSTOMER YANG TERLEWAT
|
||||
</h3>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<ListGroup variant="flush">
|
||||
{queues
|
||||
.filter((q) => q.status === "Waiting" && new Date(q.createdAt) < new Date(currentTime.getTime() - 30 * 60 * 1000))
|
||||
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
|
||||
.slice(0, 5) // Tambahkan slice(0, 5) untuk menampilkan hanya 5 data teratas
|
||||
.map((q, i) => (
|
||||
<ListGroup.Item key={i} className="d-flex justify-content-between flex-column">
|
||||
<div className="d-flex justify-content-between">
|
||||
<span className="fw-bold">{q.id}</span>
|
||||
<span className="text-muted">{q.status} - {new Date(q.createdAt).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="d-flex flex-wrap g-0">
|
||||
<Col className="d-flex p-3">
|
||||
<Card className="flex-grow-1"
|
||||
style={{ width: "100%" }}>
|
||||
<Card.Header>
|
||||
<h2>
|
||||
List Layanan antrian
|
||||
</h2>
|
||||
</Card.Header>
|
||||
<Row className="m-2">
|
||||
<Row className="g-0">
|
||||
<Col className="d-flex justify-content-center">
|
||||
<Row className="flex-grow-1" style={{ width: "100%" }}>
|
||||
{/* List Antrian Setiap Layanan */}
|
||||
{Array.from(new Set(queues.map((queue) => queue.serviceName))).map((serviceName, index) => (
|
||||
<Col key={index} md={2}>
|
||||
<h3>{serviceName}</h3>
|
||||
{queues
|
||||
.filter((q) => q.serviceName === serviceName)
|
||||
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
|
||||
.map((q, i) => (
|
||||
<span key={i}>{q.serviceId} - {q.id} - {new Date(q.createdAt).toLocaleTimeString()}</span>
|
||||
))}
|
||||
<Col key={index} md={4} className="mb-3">
|
||||
<Card className="shadow border-0">
|
||||
<Card.Header
|
||||
className={`bg-${getRandomColor(index)} text-white text-capitalize`}
|
||||
>
|
||||
<h5>
|
||||
List Antrian {serviceName}
|
||||
</h5>
|
||||
</Card.Header>
|
||||
<ListGroup variant="flush">
|
||||
{queues
|
||||
.filter((q) => q.status === "Waiting")
|
||||
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
|
||||
.slice(0, 7) // Tambahkan slice(0, 5) untuk membatasi jumlah data
|
||||
.map((q, i) => (
|
||||
<ListGroup.Item key={i} className="d-flex justify-content-between flex-column">
|
||||
<div className="d-flex justify-content-between">
|
||||
<span className="fw-bold">{q.id}</span>
|
||||
<span className="text-muted">{q.status} - {new Date(q.createdAt).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
@ -18,6 +18,7 @@ const ServiceSelection = () => {
|
||||
const [phone, setPhone] = useState("");
|
||||
const [ticket, setTicket] = useState(null);
|
||||
const [enableForm, setEnableForm] = useState(false);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [queues, setQueues] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -57,7 +58,7 @@ const ServiceSelection = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container fluid className="bg-dark text-light">
|
||||
<Container fluid style={{backgroundColor: "#E3F2FD"}}>
|
||||
{!ticket ? (
|
||||
<Row className="vh-100">
|
||||
<Col md={6} className="d-flex flex-column">
|
||||
@ -65,13 +66,15 @@ const ServiceSelection = () => {
|
||||
{services.map(service => (
|
||||
<Col key={service.id} md={6} className="my-2">
|
||||
<Card
|
||||
className="h-100 shadow rounded-3 bg-secondary"
|
||||
className="h-100 shadow rounded-3 bg-light"
|
||||
onClick={() => handleServiceSelect(service.id)}
|
||||
>
|
||||
<Card.Body className="d-flex flex-column justify-content-center align-items-center">
|
||||
<Card.Body className="d-flex flex-column justify-content-center align-items-center text-primary">
|
||||
{service.icon}
|
||||
<Card.Text className="text-center mt-5">
|
||||
{service.name}
|
||||
<Card.Text>
|
||||
<h5 className="text-center mt-4 text-dark fw-bold">
|
||||
{service.name}
|
||||
</h5>
|
||||
</Card.Text>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
@ -79,54 +82,54 @@ const ServiceSelection = () => {
|
||||
))}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col md={6} className="d-flex flex-column my-2">
|
||||
<Card className={`flex-grow-1 shadow rounded-3 ${enableForm ? "bg-secondary" : "bg-dark"}`}>
|
||||
<Card.Body className="d-flex flex-column">
|
||||
<Col md={6} className="d-flex flex-column mt-2">
|
||||
<Card className={`flex-grow-1 shadow rounded-3 ${enableForm ? "bg-light" : "bg-transparent"}`}>
|
||||
<Card.Body className="d-flex flex-column">
|
||||
<div className="d-flex align-items-center ">
|
||||
<h3 className="mb-0 me-3 text-uppercase text-white">Data Diri</h3>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
onChange={() => setEnableForm(!enableForm)}
|
||||
style={{ transform: "scale(1.5)" }}
|
||||
/>
|
||||
<p className="my-2 text-white" style={{ transform: "scale(0.9)" }}>
|
||||
Beri tanda centang untuk mengisi form
|
||||
</p>
|
||||
</div>
|
||||
<Form onSubmit={handleSubmit} className="flex-grow-1">
|
||||
<Form.Group className="mb-3" controlId="formName">
|
||||
<Form.Label className={enableForm ? "text-light" : "text-muted"}>Nama</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: pandawa"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={enableForm ? "bg-light text-dark" : "bg-dark text-muted"}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formPhone">
|
||||
<Form.Label className={enableForm ? "text-light" : "text-muted"}>No Telepon</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: 08xxxxxxxxxxx"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={enableForm ? "bg-light text-dark" : "bg-dark text-muted"}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant={enableForm ? "primary" : "secondary"} type="submit" disabled={!enableForm} className="p-4">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<div className="d-flex flex-grow-1 align-items-center justify-content-center">
|
||||
<h3 className={`mb-0 me-3 text-uppercase ${enableForm ? "text-dark" : "text-muted"}`}>Data Diri</h3>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
onChange={() => setEnableForm(!enableForm)}
|
||||
style={{ transform: "scale(1.5)" }}
|
||||
/>
|
||||
<p className={`my-2 ${enableForm ? "text-dark" : "text-muted"}`} style={{ transform: "scale(0.9)" }}>
|
||||
Beri tanda centang untuk mengisi form
|
||||
</p>
|
||||
</div>
|
||||
<Form onSubmit={handleSubmit} className="flex-grow-1">
|
||||
<Form.Group className="mb-3 mt-3" controlId="formName">
|
||||
<Form.Label className={enableForm ? "text-dark" : "text-muted"}>Nama</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: pandawa"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={enableForm ? "bg-light text-dark" : "bg-transparent text-muted"}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formPhone">
|
||||
<Form.Label className={enableForm ? "text-dark" : "text-muted"}>No Telepon</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder="@example: 08xxxxxxxxxxx"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={!enableForm}
|
||||
className={enableForm ? "bg-light text-dark" : "bg-transparent text-muted"}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant={enableForm ? "primary" : "text-muted"} type="submit" disabled={!enableForm}>
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<div className="d-flex my-2 align-items-center justify-content-center">
|
||||
<Carousel>
|
||||
{["c1.png", "c2.jpg", "c3.jpg"].map((image, index) => (
|
||||
<Carousel.Item key={index} interval={3000} className='rounded'>
|
||||
<img className="d-block img-fluid rounded" src={`/public/${image}`} alt={`Slide ${index + 1}`}/>
|
||||
<img className="img-fluid rounded" src={`/public/${image}`} alt={`Slide ${index + 1}`}/>
|
||||
</Carousel.Item>
|
||||
))}
|
||||
</Carousel>
|
||||
|
||||
17
src/components/Shared/useTheme.js
Normal file
17
src/components/Shared/useTheme.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useTheme = () => {
|
||||
const getInitialTheme = () => localStorage.getItem("theme") === "dark";
|
||||
const [darkMode, setDarkMode] = useState(getInitialTheme);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("theme", darkMode ? "dark" : "light");
|
||||
document.body.classList.toggle("dark-mode", darkMode);
|
||||
}, [darkMode]);
|
||||
|
||||
const toggleTheme = () => setDarkMode((prev) => !prev);
|
||||
|
||||
return { darkMode, toggleTheme };
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
@ -2,15 +2,22 @@
|
||||
--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%;
|
||||
.custom-pagination .page-item .page-link {
|
||||
color: #007bff;
|
||||
border: 1px solid #007bff;
|
||||
transition: all 0.3s ease;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.mb-3.p-3.text-white.bg-dark:hover .close-btn {
|
||||
opacity: 1;
|
||||
.custom-pagination .page-item.active .page-link {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.custom-pagination .page-item .page-link:hover {
|
||||
background-color: #0056b3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user