27/02/2025
This commit is contained in:
parent
167f0f4e0a
commit
7cf8029368
2
.env
2
.env
@ -1 +1 @@
|
||||
VITE_API_URL = http://localhost:3000/queue
|
||||
VITE_API_URL = http://localhost:3000
|
||||
125
db.json
125
db.json
@ -1,16 +1,115 @@
|
||||
{
|
||||
"queue": [
|
||||
{ "id": 1, "number": "P001", "counter": 1, "service": "Siap Print", "customer": { "name": "John Doe", "phone": "081234567890" }, "startTime": "2025-02-26T08:00:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 2, "number": "D001", "counter": 2, "service": "Design", "customer": { "name": "Jane Smith", "phone": "081298765432" }, "startTime": "2025-02-26T08:05:00", "endTime": null, "status": "called" },
|
||||
{ "id": 3, "number": "F001", "counter": 3, "service": "Fotocopy", "customer": { "name": "Emily Davis", "phone": "081678901234" }, "startTime": "2025-02-26T08:30:00", "endTime": "2025-02-26T08:45:00", "status": "completed" },
|
||||
{ "id": 4, "number": "R001", "counter": 4, "service": "Retur Barang", "customer": { "name": "Sophia Lee", "phone": "081789012345" }, "startTime": "2025-02-26T08:35:00", "endTime": null, "status": "called" },
|
||||
{ "id": 5, "number": "O001", "counter": 5, "service": "Online Pickup", "customer": { "name": "Daniel Martinez", "phone": "081890123456" }, "startTime": "2025-02-26T08:40:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 6, "number": "T001", "counter": 6, "service": "Tamu", "customer": { "name": "Henry Adams", "phone": "081901234567" }, "startTime": "2025-02-26T08:50:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 7, "number": "P002", "counter": 1, "service": "Siap Print", "customer": { "name": "James Thompson", "phone": "081234567891" }, "startTime": "2025-02-26T09:00:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 8, "number": "D002", "counter": 2, "service": "Design", "customer": { "name": "Benjamin Clark", "phone": "081456789013" }, "startTime": "2025-02-26T09:10:00", "endTime": "2025-02-26T09:20:00", "status": "completed" },
|
||||
{ "id": 9, "number": "F002", "counter": 3, "service": "Fotocopy", "customer": { "name": "Olivia Brown", "phone": "081567890124" }, "startTime": "2025-02-26T09:15:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 10, "number": "R002", "counter": 4, "service": "Retur Barang", "customer": { "name": "William White", "phone": "081678901235" }, "startTime": "2025-02-26T09:20:00", "endTime": null, "status": "called" },
|
||||
{ "id": 11, "number": "O002", "counter": 5, "service": "Online Pickup", "customer": { "name": "Sophia Harris", "phone": "081789012346" }, "startTime": "2025-02-26T09:25:00", "endTime": null, "status": "waiting" },
|
||||
{ "id": 12, "number": "T002", "counter": 6, "service": "Tamu", "customer": { "name": "Alexander Nelson", "phone": "081890123457" }, "startTime": "2025-02-26T09:30:00", "endTime": null, "status": "waiting" }
|
||||
"operators": [
|
||||
{
|
||||
"id": "O001",
|
||||
"name": "Rina Wijaya",
|
||||
"email": "rina@example.com",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12345",
|
||||
"customer": {
|
||||
"id": "C001",
|
||||
"name": "Budi Santoso",
|
||||
"phone": "+628123456789",
|
||||
"email": "budi@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
},
|
||||
"status": "In Progress",
|
||||
"created_at": "2025-02-27T08:30:00Z"
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12346",
|
||||
"customer": {
|
||||
"id": "C002",
|
||||
"name": "Siti Aminah",
|
||||
"phone": "+628987654321",
|
||||
"email": "siti@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T09:00:00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O002",
|
||||
"name": "Doni Saputra",
|
||||
"email": "doni@example.com",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12347",
|
||||
"customer": {
|
||||
"id": "C003",
|
||||
"name": "Ahmad Fauzi",
|
||||
"phone": "+6285566778899",
|
||||
"email": "ahmad@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "design"
|
||||
},
|
||||
"status": "Completed",
|
||||
"created_at": "2025-02-27T09:30:00Z"
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12348",
|
||||
"customer": {
|
||||
"id": "C004",
|
||||
"name": "Lina Kusuma",
|
||||
"phone": "+6289988776655",
|
||||
"email": "lina@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0003",
|
||||
"name": "fotocopy"
|
||||
},
|
||||
"status": "In Progress",
|
||||
"created_at": "2025-02-27T10:00:00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "O003",
|
||||
"name": "Sari Ningsih",
|
||||
"email": "sari@example.com",
|
||||
"queues": [
|
||||
{
|
||||
"queue_id": "Q12349",
|
||||
"customer": {
|
||||
"id": "j000",
|
||||
"name": "Rudi Hartono",
|
||||
"phone": "+6287711223344",
|
||||
"email": "rudi@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0002",
|
||||
"name": "design"
|
||||
},
|
||||
"status": "Waiting",
|
||||
"created_at": "2025-02-27T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"queue_id": "Q12350",
|
||||
"customer": {
|
||||
"id": "C006",
|
||||
"name": "Dewi Anggraini",
|
||||
"phone": "+6286677889900",
|
||||
"email": "dewi@example.com"
|
||||
},
|
||||
"service": {
|
||||
"id": "J0001",
|
||||
"name": "siap_print"
|
||||
},
|
||||
"status": "Completed",
|
||||
"created_at": "2025-02-27T11:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"cors": "^2.8.5",
|
||||
@ -1772,9 +1772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@ -6220,9 +6220,9 @@
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@ -1,56 +1,35 @@
|
||||
import axios from "axios";
|
||||
|
||||
const API_URL = "http://localhost:5000/queue";
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
export const fetchOperators = async () => {
|
||||
try {
|
||||
const response = await axios.get(API_URL);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching operators", error);
|
||||
return [];
|
||||
}
|
||||
export const fetchQueues = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/operators`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching queues:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const addCustomerToOperator = async (operatorId, customerData) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/${operatorId}`);
|
||||
const operator = response.data;
|
||||
|
||||
operator.customers.push(customerData);
|
||||
|
||||
const updateResponse = await axios.put(`${API_URL}/${operatorId}`, operator);
|
||||
return updateResponse.data;
|
||||
} catch (error) {
|
||||
console.error("Error adding customer", error);
|
||||
}
|
||||
export const updateQueueStatus = async (queueId, status) => {
|
||||
try {
|
||||
const response = await axios.put(`${API_URL}/queues/${queueId}`, {
|
||||
status,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error updating queue status:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCustomerStatus = async (operatorId, customerId, updatedData) => {
|
||||
export const createQueue = async (customerData) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/${operatorId}`);
|
||||
const operator = response.data;
|
||||
|
||||
operator.customers = operator.customers.map(customer =>
|
||||
customer.id === customerId ? { ...customer, ...updatedData } : customer
|
||||
);
|
||||
|
||||
const updateResponse = await axios.put(`${API_URL}/${operatorId}`, operator);
|
||||
return updateResponse.data;
|
||||
const response = await axios.post(`${API_URL}/queues`, customerData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error updating customer status", error);
|
||||
console.error("Error creating queue:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteCustomerFromOperator = async (operatorId, customerId) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/${operatorId}`);
|
||||
const operator = response.data;
|
||||
|
||||
operator.customers = operator.customers.filter(customer => customer.id !== customerId);
|
||||
|
||||
await axios.put(`${API_URL}/${operatorId}`, operator);
|
||||
} catch (error) {
|
||||
console.error("Error deleting customer", error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -28,9 +28,9 @@ const AdminSidebar = () => {
|
||||
<NavLink to="/admin/queue-list" className="btn btn-light shadow-sm d-flex align-items-center">
|
||||
<FaList className="me-2" /> Daftar Antrian
|
||||
</NavLink>
|
||||
<NavLink to="/admin/call-queue" className="btn btn-light shadow-sm d-flex align-items-center">
|
||||
{/* <NavLink to="/admin/call-queue" className="btn btn-light shadow-sm d-flex align-items-center">
|
||||
<FaPhone className="me-2" /> Panggil Antrian
|
||||
</NavLink>
|
||||
</NavLink> */}
|
||||
{/* <NavLink to="/admin/queue-report" className="btn btn-light shadow-sm d-flex align-items-center">
|
||||
<FaChartBar className="me-2" /> Laporan Antrian
|
||||
</NavLink> */}
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
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"},
|
||||
{ id: 1, name: "John Doe", phone: "08123456789", service: "Siap Print", serviceId: "J0001", locket: "loket 01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true},
|
||||
{ id: 2, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "loket 04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Menunggu", called: false, active: true},
|
||||
{ id: 3, name: "Michael Johnson", phone: "08345678901", service: "FotoCopy", serviceId: "J0003", locket: "loket 08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Menunggu", called: false, active: true},
|
||||
{ id: 4, name: "Emily Brown", phone: "08456789012", service: "Tamu", serviceId: "J0006", locket: "loket 02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true},
|
||||
{ id: 5, name: "David Wilson", phone: "08567890123", service: "Online", serviceId: "J0005", locket: "loket 05", startAt: "04:00 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Menunggu", called: false, active: true},
|
||||
{ id: 6, name: "Sophia Martinez", phone: "08678901234", service: "Online", serviceId: "J0005", locket: "loket 06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Menunggu", called: false, active: true},
|
||||
{ id: 7, name: "James Anderson", phone: "08789012345", service: "Siap Print", serviceId: "J0001", locket: "loket 03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true},
|
||||
{ id: 8, name: "Olivia Thomas", phone: "08890123456", service: "Design", serviceId: "J0002", locket: "loket 07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Menunggu", called: false, active: true},
|
||||
{ id: 9, name: "Liam White", phone: "08901234567", service: "FotoCopy", serviceId: "J0003", locket: "loket 09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Menunggu", called: false, active: true},
|
||||
{ id: 10, name: "Emma Harris", phone: "08012345678", service: "Siap Print", serviceId: "J0001", locket: "loket 10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true},
|
||||
{ id: 11, name: "John Doe", phone: "08123456789", service: "Siap Print", serviceId: "J0001", locket: "loket 01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true},
|
||||
{ id: 12, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "loket 04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Menunggu", called: false, active: true},
|
||||
{ id: 13, name: "Michael Johnson", phone: "08345678901", service: "FotoCopy", serviceId: "J0003", locket: "loket 08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Menunggu", called: false, active: true},
|
||||
{ id: 14, name: "Emily Brown", phone: "08456789012", service: "Online", serviceId: "J0005", locket: "loket 02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true},
|
||||
{ id: 15, name: "David Wilson", phone: "08567890123", service: "Design", serviceId: "J0002", locket: "loket 05", startAt: "04:00 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Menunggu", called: false, active: true},
|
||||
{ id: 16, name: "John Doe", phone: "08123456789", service: "Online", serviceId: "J0005", locket: "loket 01", startAt: "10:30 AM", completedAt: "10:30 AM", queueNumber: "001", status: "Menunggu", called: false, active: true},
|
||||
{ id: 17, name: "Jane Smith", phone: "08234567890", service: "Design", serviceId: "J0002", locket: "loket 04", startAt: "11:15 AM", completedAt: "11:15 AM", queueNumber: "002", status: "Menunggu", called: false, active: true},
|
||||
{ id: 18, name: "Michael Johnson", phone: "08345678901", service: "Retur", serviceId: "J0004", locket: "loket 08", startAt: "11:50 AM", completedAt: "11:50 AM", queueNumber: "003", status: "Menunggu", called: false, active: true},
|
||||
{ id: 19, name: "Emily Brown", phone: "08456789012", service: "Online", serviceId: "J0005", locket: "loket 02", startAt: "12:10 PM", completedAt: "12:10 PM", queueNumber: "004", status: "Menunggu", called: false, active: true},
|
||||
{ id: 20, name: "David Wilson", phone: "08567890123", service: "Design", serviceId: "J0002", locket: "loket 05", startAt: "04:00 PM", completedAt: "12:50 PM", queueNumber: "005", status: "Menunggu", called: false, active: true},
|
||||
{ id: 21, name: "Sophia Martinez", phone: "08678901234", service: "Retur", serviceId: "J0004", locket: "loket 06", startAt: "01:30 PM", completedAt: "01:30 PM", queueNumber: "006", status: "Menunggu", called: false, active: true},
|
||||
{ id: 22, name: "James Anderson", phone: "08789012345", service: "Online", serviceId: "J0005", locket: "loket 03", startAt: "02:15 PM", completedAt: "02:15 PM", queueNumber: "007", status: "Menunggu", called: false, active: true},
|
||||
{ id: 23, name: "Olivia Thomas", phone: "08890123456", service: "Design", serviceId: "J0002", locket: "loket 07", startAt: "02:50 PM", completedAt: "02:50 PM", queueNumber: "008", status: "Menunggu", called: false, active: true},
|
||||
{ id: 24, name: "Liam White", phone: "08901234567", service: "Tamu", serviceId: "J0006", locket: "loket 09", startAt: "03:20 PM", completedAt: "03:20 PM", queueNumber: "009", status: "Menunggu", called: false, active: true},
|
||||
{ id: 25, name: "Emma Harris", phone: "08012345678", service: "Siap Print", serviceId: "J0001", locket: "loket 10", startAt: "03:50 PM", completedAt: "03:50 PM", queueNumber: "010", status: "Menunggu", called: false, active: true},
|
||||
];
|
||||
|
||||
export const getQueueSettingsData = () => {
|
||||
@ -61,10 +61,21 @@ export const toggleServiceStatus = (id) => {
|
||||
};
|
||||
|
||||
export const getFilteredQueueData = (callQueueView = false) => {
|
||||
if (callQueueView) {
|
||||
return queueData.filter((item) => item.status === "Menunggu");
|
||||
}
|
||||
return queueData;
|
||||
let filteredQueue = queueData.filter((item) =>
|
||||
callQueueView ? item.status === "Menunggu" : true
|
||||
);
|
||||
|
||||
// Urutkan berdasarkan status "Menunggu" terlebih dahulu, lalu berdasarkan startAt
|
||||
filteredQueue.sort((a, b) => {
|
||||
if (a.status === "Menunggu" && b.status !== "Menunggu") return -1;
|
||||
if (b.status === "Menunggu" && a.status !== "Menunggu") return 1;
|
||||
|
||||
const timeA = new Date(`1970/01/01 ${a.startAt}`);
|
||||
const timeB = new Date(`1970/01/01 ${b.startAt}`);
|
||||
return timeA - timeB;
|
||||
});
|
||||
|
||||
return filteredQueue;
|
||||
};
|
||||
|
||||
export const getQueueDisplayData = () => {
|
||||
@ -79,9 +90,6 @@ export const getQueueDisplayData = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
// QueueActions.js
|
||||
import { useState } from "react";
|
||||
|
||||
export const useQueueData = (settingsView, callQueueView) => {
|
||||
@ -113,7 +121,7 @@ export const useQueueData = (settingsView, callQueueView) => {
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
setCurrentPage,
|
||||
indexOfFirstItem, // ✅ Tambahkan ini
|
||||
indexOfFirstItem,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,142 +1,143 @@
|
||||
import { Table, Button, Form, Pagination, InputGroup, FormControl } from "react-bootstrap";
|
||||
import { useState } from "react";
|
||||
import { Card, Table, Button, Form, Pagination, InputGroup, FormControl, Modal } from "react-bootstrap";
|
||||
import { FaSearch } from "react-icons/fa";
|
||||
import { handleCall, toggleServiceStatus, handleDelete, handleEdit, useQueueData } from "./QueueActions";
|
||||
import { handleCall, 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);
|
||||
const QueueTable = ({ settingsView = false, displayView = false }) => {
|
||||
const { currentItems, currentPage, totalPages, searchQuery, setSearchQuery, setCurrentPage, indexOfFirstItem } = useQueueData(settingsView);
|
||||
|
||||
// State untuk modal
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [selectedQueue, setSelectedQueue] = useState(null);
|
||||
const [selectedLocket, setSelectedLocket] = useState("");
|
||||
|
||||
// Data loket operator (bisa diganti dengan API)
|
||||
const availableLockets = ["Loket 1", "Loket 2", "Loket 3", "Loket 4", "Loket 5", "Loket 6", "Loket 7", "Loket 8", "Loket 9"];
|
||||
|
||||
// Fungsi membuka modal dan menyimpan antrian yang dipilih
|
||||
const handleOpenModal = (queue) => {
|
||||
setSelectedQueue(queue);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
// Fungsi menutup modal
|
||||
const handleCloseModal = () => {
|
||||
setShowModal(false);
|
||||
setSelectedQueue(null);
|
||||
setSelectedLocket("");
|
||||
};
|
||||
|
||||
// Fungsi memilih loket dan panggil antrian
|
||||
const handleSelectLocket = () => {
|
||||
if (selectedLocket) {
|
||||
handleCall(selectedQueue.id, selectedLocket); // Panggil dengan loket
|
||||
handleCloseModal(); // Tutup modal setelah memilih
|
||||
}
|
||||
};
|
||||
|
||||
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 && (
|
||||
<Card className="shadow-sm">
|
||||
<Card.Header className="d-flex justify-content-between align-items-center">
|
||||
{(!settingsView && !displayView) && (
|
||||
<InputGroup className="w-auto">
|
||||
<InputGroup.Text>
|
||||
<FaSearch />
|
||||
</InputGroup.Text>
|
||||
<FormControl
|
||||
type="text"
|
||||
placeholder="Cari nama atau layanan..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
<Pagination className="mb-0">
|
||||
<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>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Body style={{ minHeight: "500px", display: "flex", flexDirection: "column" }}>
|
||||
<div style={{ flex: "1", overflowY: "auto" }}>
|
||||
<Table striped bordered hover responsive>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Nama</th>
|
||||
<th>Layanan</th>
|
||||
<th>Kode Layanan</th>
|
||||
<th>Nomor Antrian</th>
|
||||
<th>Pengambilan</th>
|
||||
<th>Loket</th>
|
||||
<th>Status</th>
|
||||
<th className="text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentItems.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td>{index + 1 + indexOfFirstItem}</td>
|
||||
<td>{item.name}</td>
|
||||
<td>{item.service}</td>
|
||||
<td>{item.serviceId}</td>
|
||||
<td>{item.queueNumber}</td>
|
||||
<td>{item.startAt}</td>
|
||||
<td>{item.status === "Dilayani" ? item.locket : "-"}</td>
|
||||
<td>{item.status}</td>
|
||||
<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 variant="primary" size="sm" className="me-2" onClick={() => handleOpenModal(item)}>
|
||||
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>
|
||||
</tr>
|
||||
))}
|
||||
{Array.from({ length: Math.max(0, 10 - currentItems.length) }).map((_, index) => (
|
||||
<tr key={`empty-${index}`} className="empty-row">
|
||||
<td colSpan="9"> </td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
||||
{/* Modal untuk memilih loket */}
|
||||
<Modal show={showModal} onHide={handleCloseModal} centered>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Pilih Loket Operator</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form>
|
||||
{availableLockets.map((locket, index) => (
|
||||
<Form.Check
|
||||
key={index}
|
||||
type="radio"
|
||||
label={locket}
|
||||
name="locket"
|
||||
value={locket}
|
||||
checked={selectedLocket === locket}
|
||||
onChange={(e) => setSelectedLocket(e.target.value)}
|
||||
/>
|
||||
))}
|
||||
</Form>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleCloseModal}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSelectLocket} disabled={!selectedLocket}>
|
||||
Panggil
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
30
src/components/Admin/TestQueueActions.jsx
Normal file
30
src/components/Admin/TestQueueActions.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
|
||||
|
||||
import { fetchQueues, updateQueueStatus } from "../../api/queueApi";
|
||||
|
||||
export const getProcessedQueues = async () => {
|
||||
const data = await fetchQueues();
|
||||
|
||||
// Ubah struktur data jika diperlukan
|
||||
const processedData = data.map((operator) => ({
|
||||
operatorId: operator.id,
|
||||
operatorName: operator.name,
|
||||
queues: operator.queues.map((queue) => ({
|
||||
id: queue.queue_id,
|
||||
customerName: queue.customer.name,
|
||||
customerEmail: queue.customer.email,
|
||||
customerPhone: queue.customer.phone,
|
||||
serviceId: queue.service.id,
|
||||
serviceName: queue.service.name,
|
||||
status: queue.status,
|
||||
createdAt: queue.created_at,
|
||||
})),
|
||||
}));
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
export const changeQueueStatus = async (queueId, newStatus) => {
|
||||
return await updateQueueStatus(queueId, newStatus);
|
||||
};
|
||||
133
src/components/Admin/TestQueueTable.jsx
Normal file
133
src/components/Admin/TestQueueTable.jsx
Normal file
@ -0,0 +1,133 @@
|
||||
|
||||
// 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";
|
||||
|
||||
const TestQueueTable = () => {
|
||||
const [queues, setQueues] = useState([]);
|
||||
const [selectedQueue, setSelectedQueue] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
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
|
||||
}))
|
||||
);
|
||||
// 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);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="mt-4">
|
||||
<Card className="shadow">
|
||||
<Card.Header className="bg-primary text-white">
|
||||
<h5 className="mb-0">List Antrian</h5>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Table striped bordered hover size="sm" responsive>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Service</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{queues.length > 0 ? (
|
||||
queues.map((queue) => (
|
||||
<tr key={queue.id}>
|
||||
<td>{queue.customerName}</td>
|
||||
<td>{queue.customerEmail}</td>
|
||||
<td>{queue.customerPhone}</td>
|
||||
<td>{queue.serviceName}</td>
|
||||
<td>
|
||||
<span
|
||||
className={`badge ${
|
||||
queue.status === "Completed"
|
||||
? "bg-success"
|
||||
: queue.status === "In Progress"
|
||||
? "bg-warning text-dark"
|
||||
: "bg-danger"
|
||||
}`}
|
||||
>
|
||||
{queue.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>{new Date(queue.createdAt).toLocaleString()}</td>
|
||||
<td>
|
||||
{queue.status !== "In Progress" && queue.status !== "Completed" && (
|
||||
<Button
|
||||
variant="info"
|
||||
size="sm"
|
||||
onClick={() => handleCallQueue(queue)}
|
||||
>
|
||||
Panggil Antrian
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="9" className="text-center text-muted">
|
||||
No queues available
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
||||
{/* Modal Pilih Loket */}
|
||||
<Modal show={showModal} onHide={() => setShowModal(false)} centered>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Pilih Loket Operator</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>Memanggil antrian <strong>{selectedQueue?.customerName}</strong></p>
|
||||
<ListGroup>
|
||||
{availableCounters.map((counter) => (
|
||||
<ListGroup.Item key={counter.id} action>
|
||||
{counter.name}
|
||||
</ListGroup.Item>
|
||||
))}
|
||||
</ListGroup>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestQueueTable;
|
||||
@ -80,19 +80,53 @@ const QueueDisplay = () => {
|
||||
</Row>
|
||||
|
||||
{/* Loket Antrian */}
|
||||
<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" }}>
|
||||
<Row className="d-flex flex-wrap g-0">
|
||||
<Col className="d-flex p-2">
|
||||
<Card className="flex-grow-1"
|
||||
style={{ borderRadius: "10px", width: "100%", minHeight: "150px" }}>
|
||||
<Card.Title className="p-3">
|
||||
<h2>
|
||||
List Antrian Layanan
|
||||
</h2>
|
||||
</Card.Title>
|
||||
<Row>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<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>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
<Col>
|
||||
<Card.Body>
|
||||
<Card.Title>List Antrian Layanan</Card.Title>
|
||||
<p>data antrian</p>
|
||||
</Card.Body>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Container, Card, Button, Row, Col, Form, Carousel } from 'react-bootstrap';
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FaPrint, FaPalette, FaFileAlt, FaUndo, FaTruck, FaUser } from 'react-icons/fa';
|
||||
|
||||
const services = [
|
||||
@ -18,15 +18,17 @@ const ServiceSelection = () => {
|
||||
const [ticket, setTicket] = useState(null);
|
||||
const [enableForm, setEnableForm] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedService && !enableForm) {
|
||||
generateTicket();
|
||||
}
|
||||
}, [selectedService]);
|
||||
|
||||
const handleServiceSelect = (serviceId) => {
|
||||
setSelectedService(services.find(service => service.id === serviceId));
|
||||
if (!enableForm) {
|
||||
handleSubmit(new Event("submit"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const generateTicket = () => {
|
||||
if (selectedService) {
|
||||
const queueNumber = `${selectedService.id}-${Math.floor(1000 + Math.random() * 9000)}`;
|
||||
const newTicket = {
|
||||
@ -41,31 +43,35 @@ const ServiceSelection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
generateTicket();
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{!ticket ? (
|
||||
<Row className="vh-100">
|
||||
<Col md={6} className="d-flex flex-column">
|
||||
<h3 className="my-4">Pilih Layanan</h3>
|
||||
<div className="my-4">
|
||||
<h3>Pilih Layanan</h3>
|
||||
</div>
|
||||
<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 shadow rounded-3 ${isSelected ? "bg-primary text-white border-primary" : "bg-light"}`}
|
||||
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">
|
||||
<h5>{service.name}</h5>
|
||||
</Card.Text>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
{services.map(service => (
|
||||
<Col key={service.id} md={6} className="mb-3">
|
||||
<Card
|
||||
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">
|
||||
{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">
|
||||
@ -91,7 +97,6 @@ const ServiceSelection = () => {
|
||||
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">
|
||||
@ -102,7 +107,6 @@ const ServiceSelection = () => {
|
||||
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}>
|
||||
@ -111,11 +115,11 @@ const ServiceSelection = () => {
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<div className="d-flex flex-grow-1 my-3 align-items-center justify-content-center">
|
||||
<Carousel>
|
||||
<div className="d-flex flex-grow-1 align-items-center justify-content-center">
|
||||
<Carousel className='w-100'>
|
||||
{["c1.png", "c2.jpg", "c3.jpg"].map((image, index) => (
|
||||
<Carousel.Item key={index} interval={3000}>
|
||||
<img className="d-block w-100" src={`/public/${image}`} alt={`Slide ${index + 1}`} height={300} />
|
||||
<Carousel.Item key={index} interval={3000} className='rounded'>
|
||||
<img className="d-block w-100 img-fluid rounded" src={`/public/${image}`} alt={`Slide ${index + 1}`}/>
|
||||
</Carousel.Item>
|
||||
))}
|
||||
</Carousel>
|
||||
|
||||
45
src/components/Display/TestQueueDisplay.jsx
Normal file
45
src/components/Display/TestQueueDisplay.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
// FILE INI UNTUK TEST TAMPILAN DATA DENGAN API
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getProcessedQueues } from "../Admin/TestQueueActions";
|
||||
import { Card, Container } from "react-bootstrap";
|
||||
|
||||
const TestQueueDisplay = () => {
|
||||
const [queues, setQueues] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await getProcessedQueues();
|
||||
// Ambil hanya antrian yang masih berlangsung atau menunggu, lalu urutkan
|
||||
const sortedQueues = data
|
||||
.flatMap((operator) => operator.queues)
|
||||
.filter((queue) => queue.status !== "Completed")
|
||||
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
||||
|
||||
setQueues(sortedQueues);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
const interval = setInterval(fetchData, 2000); // Auto-refresh tiap 5 detik
|
||||
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>
|
||||
</Card>
|
||||
) : (
|
||||
<p className="text-muted">Tidak ada antrian aktif</p>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestQueueDisplay;
|
||||
|
||||
34
src/components/Display/TestServiceSelection.jsx
Normal file
34
src/components/Display/TestServiceSelection.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
// 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";
|
||||
|
||||
const ServiceSelection = () => {
|
||||
const [queues, setQueues] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
getProcessedQueues().then(setQueues);
|
||||
}, []);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceSelection;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import QueueTable from "../../components/Admin/QueueTable";
|
||||
import TestQueueTable from "../../components/Admin/TestQueueTable";
|
||||
|
||||
const CallQueuePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Panggil Atrian Page</h2>
|
||||
<QueueTable callQueueView={true}/>
|
||||
<TestQueueTable/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import TestQueueTable from "../../components/Admin/TestQueueTable";
|
||||
import QueueTable from "../../components/Admin/QueueTable";
|
||||
|
||||
const QueueListPage = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Daftar Antrian</h2>
|
||||
<QueueTable showActions={false}/>
|
||||
|
||||
{/* <TestQueueTable/> INI ADALAH UNTUK TEST DATA DENGAN API */}
|
||||
|
||||
<QueueTable/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import TestQueueDisplay from "../../components/Display/TestQueueDisplay"
|
||||
import QueueDisplay from "../../components/Display/QueueDisplay"
|
||||
|
||||
const QueueDisplayPage = () => {
|
||||
return (
|
||||
<>
|
||||
{/* <TestQueueDisplay/> INI ADALAH UNTUK TEST DATA DENGAN API */}
|
||||
<QueueDisplay/>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import TestServiceSelection from "../../components/Display/TestServiceSelection"
|
||||
import ServiceSelection from "../../components/Display/ServiceSelection"
|
||||
|
||||
const QueueMenuPage = () => {
|
||||
return (
|
||||
<>
|
||||
{/* <TestServiceSelection/> INI ADALAH UNTUK TEST DATA DENGAN API */}
|
||||
<ServiceSelection/>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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 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";
|
||||
@ -18,8 +18,8 @@ const AppRoutes = () => {
|
||||
{/* 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="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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user