Update 28/02/2025

This commit is contained in:
damarrsyh 2025-02-28 16:35:50 +07:00
parent c31bd10c5c
commit a0e301b401
11 changed files with 648 additions and 139 deletions

406
db.json
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
public/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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