styling yang menggunakan data API

This commit is contained in:
damarrsyh 2025-02-27 23:30:16 +07:00
parent 7cf8029368
commit c31bd10c5c
14 changed files with 255 additions and 54 deletions

3
.env
View File

@ -1 +1,2 @@
VITE_API_URL = http://localhost:3000
VITE_API_URL = http://localhost:3000
VITE_API_TEST_URL = http://localhost:3001

12
package-lock.json generated
View File

@ -15,6 +15,7 @@
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express-validator": "^7.2.1",
"idb": "^8.0.2",
"jsonwebtoken": "^9.0.2",
"react": "^19.0.0",
"react-bootstrap": "^2.10.9",
@ -3038,6 +3039,12 @@
"node": ">= 0.4"
}
},
"node_modules/idb": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.2.tgz",
"integrity": "sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==",
"license": "ISC"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -7088,6 +7095,11 @@
"function-bind": "^1.1.2"
}
},
"idb": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.2.tgz",
"integrity": "sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg=="
},
"ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",

View File

@ -17,6 +17,7 @@
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express-validator": "^7.2.1",
"idb": "^8.0.2",
"jsonwebtoken": "^9.0.2",
"react": "^19.0.0",
"react-bootstrap": "^2.10.9",

BIN
queueapp.zip Normal file

Binary file not shown.

View File

@ -24,7 +24,7 @@ export const updateQueueStatus = async (queueId, status) => {
}
};
export const createQueue = async (customerData) => {
export const createTicketApi = async (customerData) => {
try {
const response = await axios.post(`${API_URL}/queues`, customerData);
return response.data;

View File

@ -34,12 +34,12 @@ const AdminSidebar = () => {
{/* <NavLink to="/admin/queue-report" className="btn btn-light shadow-sm d-flex align-items-center">
<FaChartBar className="me-2" /> Laporan Antrian
</NavLink> */}
<NavLink to="/admin/queue-settings-display" className="btn btn-light shadow-sm d-flex align-items-center">
{/* <NavLink to="/admin/queue-settings-display" className="btn btn-light shadow-sm d-flex align-items-center">
<FaCog className="me-2" /> Pengaturan Layar
</NavLink>
<NavLink to="/admin/queue-settings-menu" className="btn btn-light shadow-sm d-flex align-items-center">
<FaTools className="me-2" /> Pengaturan Menu
</NavLink>
</NavLink> */}
</div>
)}
</div>

View File

@ -1,7 +1,6 @@
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
import { fetchQueues, updateQueueStatus } from "../../api/queueApi";
import { fetchQueues, updateQueueStatus, createTicketApi } from "../../api/queueApi";
export const getProcessedQueues = async () => {
const data = await fetchQueues();
@ -28,3 +27,7 @@ export const getProcessedQueues = async () => {
export const changeQueueStatus = async (queueId, newStatus) => {
return await updateQueueStatus(queueId, newStatus);
};
export const createTicket = async (ticketData) => {
return await createTicketApi(ticketData);
};

View File

@ -45,7 +45,7 @@ const TestQueueTable = () => {
};
return (
<Container className="mt-4">
<Container>
<Card className="shadow">
<Card.Header className="bg-primary text-white">
<h5 className="mb-0">List Antrian</h5>
@ -85,7 +85,7 @@ const TestQueueTable = () => {
</span>
</td>
<td>{new Date(queue.createdAt).toLocaleString()}</td>
<td>
<td className="text-center p-2">
{queue.status !== "In Progress" && queue.status !== "Completed" && (
<Button
variant="info"

View File

@ -1,12 +1,11 @@
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
import { useEffect, useState } from "react";
import { getProcessedQueues } from "../Admin/TestQueueActions";
import { Card, Container } from "react-bootstrap";
import { Card, Container, Row, Col, ListGroup } from "react-bootstrap";
import ReactPlayer from "react-player";
const TestQueueDisplay = () => {
const [queues, setQueues] = useState([]);
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const fetchData = async () => {
@ -25,21 +24,81 @@ const TestQueueDisplay = () => {
return () => clearInterval(interval);
}, []);
useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<Container className="mt-4 text-center">
<h2 className="mb-4">Layar Antrian</h2>
{queues.length > 0 ? (
<Card className="shadow p-4">
<h1 className="display-3 text-primary">{queues[0].id}</h1>
<h4>{queues[0].customerName}</h4>
<p>{queues[0].serviceName}</p>
<Container fluid className="p-3" style={{ overflowX: "hidden" }}>
<Row className="mb-3 g-0">
<Col md={4} className="px-3 d-flex flex-column justify-content-between">
<div
className="shadow mb-3 p-3 d-flex justify-content-between align-items-center position-relative"
style={{ borderRadius: "10px" }}
>
<div className="flex-grow-1">
<h5>Pandawa24Jam</h5>
<p>CS: 081234567891</p>
</div>
<div className="position-absolute top-50 end-0 translate-middle-y me-3">
<h4 className="mb-0">{currentTime.toLocaleTimeString()}</h4>
</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>
) : (
<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">
<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>
</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">
{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>
))}
</Row>
</Card>
) : (
<p className="text-muted">Tidak ada antrian aktif</p>
)}
</Col>
</Row>
</Container>
);
};
export default TestQueueDisplay;

View File

@ -1,34 +1,155 @@
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
import { useEffect, useState } from "react";
import { getProcessedQueues } from "../Admin/TestQueueActions";
import { Container, Card, Row, Col, Table } from "react-bootstrap";
import { getProcessedQueues, createTicket } from "../Admin/TestQueueActions";
import { Container, Card, Row, Col, Form, Button, Carousel } from "react-bootstrap";
import { FaPrint, FaPalette, FaFileAlt, FaUndo, FaTruck, FaUser } from 'react-icons/fa';
const services = [
{ 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 [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [ticket, setTicket] = useState(null);
const [enableForm, setEnableForm] = useState(false);
const [queues, setQueues] = useState([]);
useEffect(() => {
if (selectedService && !enableForm) {
generateTicket();
}
getProcessedQueues().then(setQueues);
}, []);
}, [selectedService]);
const handleServiceSelect = (serviceId) => {
setSelectedService(services.find(service => service.id === serviceId));
};
const generateTicket = async () => {
if (selectedService) {
const queueNumber = `${selectedService.id}-${Math.floor(1000 + Math.random() * 9000)}`;
const newTicket = {
number: queueNumber,
serviceId: selectedService.id,
service: selectedService.name,
name: enableForm ? name : "-",
phone: enableForm ? phone : "-",
};
try {
const response = await createTicket(newTicket);
setTicket(response.data);
} catch (error) {
console.error(error);
}
}
};
const handleSubmit = (e) => {
e.preventDefault();
generateTicket();
};
return (
<Container>
<Row>
{queues.map((operator) => (
<Col key={operator.operatorId}>
<h3>{operator.operatorName}</h3>
<ul>
{operator.queues.map((queue) => (
<li key={queue.id}>{queue.serviceName}</li>
))}
</ul>
</Col>
))}
</Row>
<Container fluid className="bg-dark text-light">
{!ticket ? (
<Row className="vh-100">
<Col md={6} className="d-flex flex-column">
<Row className="flex-grow-1">
{services.map(service => (
<Col key={service.id} md={6} className="my-2">
<Card
className="h-100 shadow rounded-3 bg-secondary"
onClick={() => handleServiceSelect(service.id)}
>
<Card.Body className="d-flex flex-column justify-content-center align-items-center">
{service.icon}
<Card.Text className="text-center mt-5">
{service.name}
</Card.Text>
</Card.Body>
</Card>
</Col>
))}
</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">
<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">
<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}`}/>
</Carousel.Item>
))}
</Carousel>
</div>
</Col>
</Row>
) : (
<Container className="d-flex justify-content-center align-items-center vh-100">
<Card className="p-4 text-center shadow-lg rounded-3 bg-secondary" style={{ maxWidth: "400px", width: "100%" }}>
<Card.Body>
<Card.Title className="fw-bold fs-3 text-uppercase">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;

View File

@ -1,14 +1,16 @@
import TestQueueTable from "../../components/Admin/TestQueueTable";
import QueueTable from "../../components/Admin/QueueTable";
// import QueueTable from "../../components/Admin/QueueTable";
// import TestComponent from "../../components/test";
const QueueListPage = () => {
return (
<div>
{/* <TestQueueTable/> INI ADALAH UNTUK TEST DATA DENGAN API */}
<QueueTable/>
<TestQueueTable/>
{/* Atas Data dengan API bawah Data Static */}
{/* <QueueTable/>
<TestComponent/> */}
</div>
);
};

View File

@ -1,11 +1,12 @@
import TestQueueDisplay from "../../components/Display/TestQueueDisplay"
import QueueDisplay from "../../components/Display/QueueDisplay"
// import QueueDisplay from "../../components/Display/QueueDisplay"
const QueueDisplayPage = () => {
return (
<>
{/* <TestQueueDisplay/> INI ADALAH UNTUK TEST DATA DENGAN API */}
<QueueDisplay/>
<TestQueueDisplay/>
{/* Atas Data dengan API bawah Data Static */}
{/* <QueueDisplay/> */}
</>
)
}

View File

@ -1,11 +1,12 @@
import TestServiceSelection from "../../components/Display/TestServiceSelection"
import ServiceSelection from "../../components/Display/ServiceSelection"
// import ServiceSelection from "../../components/Display/ServiceSelection"
const QueueMenuPage = () => {
return (
<>
{/* <TestServiceSelection/> INI ADALAH UNTUK TEST DATA DENGAN API */}
<ServiceSelection/>
<TestServiceSelection/>
{/* Atas Data dengan API bawah Data Static */}
{/* <ServiceSelection/> */}
</>
)
}

View File

@ -13,7 +13,7 @@ const AppRoutes = () => {
<Router>
<Routes>
{/* Redirect default ke queue-list */}
<Route path="/admin" element={<Navigate to="/admin/queue-list" replace />} />
<Route path="/" element={<Navigate to="/admin/queue-list" replace />} />
{/* Rute untuk Admin */}
<Route path="/admin" element={<AdminLayout />}>