fix & feature: invoice
This commit is contained in:
parent
24d9bf4d12
commit
c64cfea2a9
@ -0,0 +1,26 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
export function middleware(req: NextRequest) {
|
||||||
|
const role = req.cookies.get('role')?.value;
|
||||||
|
|
||||||
|
const url = req.nextUrl.clone();
|
||||||
|
|
||||||
|
if (url.pathname === '/booking' || url.pathname === '/all-registration') {
|
||||||
|
if (role === 'admin' && url.pathname !== '/all-registration') {
|
||||||
|
url.pathname = '/all-registration';
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
}
|
||||||
|
if (role === 'user' && url.pathname !== '/booking') {
|
||||||
|
url.pathname = '/booking';
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: config where middleware runs
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/booking', '/all-registration'],
|
||||||
|
};
|
||||||
@ -13,6 +13,14 @@ import Image from "next/image";
|
|||||||
import Badge from "@/components/ui/badge/Badge";
|
import Badge from "@/components/ui/badge/Badge";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { ChevronDownIcon, PencilIcon } from "@/icons";
|
||||||
|
import { Modal } from "@/components/ui/modal";
|
||||||
|
import { useModal } from "@/hooks/useModal";
|
||||||
|
import Button from "@/components/ui/button/Button";
|
||||||
|
import Label from "@/components/form/Label";
|
||||||
|
import Input from "@/components/form/input/InputField";
|
||||||
|
import Select from "@/components/form/Select";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
@ -32,18 +40,113 @@ interface Item {
|
|||||||
|
|
||||||
|
|
||||||
export default function BasicTableOne() {
|
export default function BasicTableOne() {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const { isOpen, openModal, closeModal } = useModal();
|
||||||
|
|
||||||
const [registration, setRegistration] = useState([])
|
const [registration, setRegistration] = useState([])
|
||||||
|
const [dataModal, setDataModal] = useState({})
|
||||||
|
const [action, setAction] = useState([])
|
||||||
|
const [actionOption, setActionOption] = useState([])
|
||||||
|
const [invoiceItems, setInvoiceItems] = useState([{ description: "", quantity: 1, price: 0 }]);
|
||||||
|
|
||||||
const getAllRegistration = async () =>{
|
const addItem = () => {
|
||||||
const result = await axios.get("/api/registration")
|
setInvoiceItems([...invoiceItems, { description: "", quantity: 1, price: 0 }]);
|
||||||
|
};
|
||||||
|
|
||||||
setRegistration(result.data.data)
|
|
||||||
|
const getAllRegistration = async () => {
|
||||||
|
const resultRegistration = await axios.get("/api/registration")
|
||||||
|
const resultAction = await axios.get("/api/action")
|
||||||
|
|
||||||
|
const selectAction = resultAction.data.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.NamaTindakan,
|
||||||
|
value: item.IdTindakan,
|
||||||
|
price: item.TarifTindakan, // <- Tambahkan harga di sini
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setAction(resultAction.data.data)
|
||||||
|
setActionOption(selectAction)
|
||||||
|
setRegistration(resultRegistration.data.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getAllRegistration()
|
getAllRegistration()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleOpenModal = (data: any) => {
|
||||||
|
openModal()
|
||||||
|
setDataModal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteRow = (index: number) => {
|
||||||
|
const newItems = [...invoiceItems];
|
||||||
|
newItems.splice(index, 1);
|
||||||
|
setInvoiceItems(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleItemChange = (index, field, value) => {
|
||||||
|
console.log({ index, field, value })
|
||||||
|
setInvoiceItems((prevItems) => {
|
||||||
|
const updatedItems = [...prevItems];
|
||||||
|
const currentItem = { ...updatedItems[index] };
|
||||||
|
|
||||||
|
if (field === "description") {
|
||||||
|
currentItem.description = value; // value ini object {label, value, price}
|
||||||
|
|
||||||
|
const prices = action.find((item: any) => {
|
||||||
|
console.log(item, "p");
|
||||||
|
return item.IdTindakan === Number(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// console.log(prices, "prices", action,value)
|
||||||
|
// Simpan harga dasar action
|
||||||
|
currentItem.actionPrice = prices.TarifTindakan || 0;
|
||||||
|
|
||||||
|
// Hitung price awal berdasarkan quantity
|
||||||
|
currentItem.price = (currentItem.quantity || 1) * currentItem.actionPrice;
|
||||||
|
|
||||||
|
} else if (field === "quantity") {
|
||||||
|
currentItem.quantity = value;
|
||||||
|
|
||||||
|
// Hitung ulang price kalau ada actionPrice
|
||||||
|
if (currentItem.actionPrice) {
|
||||||
|
currentItem.price = value * currentItem.actionPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedItems[index] = currentItem;
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitToTrTransaksi = async () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () =>{
|
||||||
|
// invoiceItems.forEach(item => {
|
||||||
|
// const newData = {
|
||||||
|
// IdRegistrasi: Number(dataModal.IdRegistrasi),
|
||||||
|
// IdTindakan: Number(item.IdTindakan), // misalnya ini IdTindakan, sesuaikan kalau beda
|
||||||
|
// JmlTindakan: Number(item.quantity),
|
||||||
|
// Price: Number(item.price),
|
||||||
|
// IdPegawai: Number(dataModal.IdPegawai)
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Submit satu-satu, contoh kalau pakai API:
|
||||||
|
// submitToTrTransaksi(newData);
|
||||||
|
// });
|
||||||
|
// console.log(invoiceItems, "invoiceItems", newData)
|
||||||
|
router.push("/invoice")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||||
<div className="max-w-full overflow-x-auto">
|
<div className="max-w-full overflow-x-auto">
|
||||||
@ -147,7 +250,9 @@ export default function BasicTableOne() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-4 py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
<TableCell className="px-4 py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||||
{item.action}
|
<Button size="sm" variant="outline" onClick={() => handleOpenModal(item)}>
|
||||||
|
<PencilIcon />
|
||||||
|
</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
@ -155,6 +260,152 @@ export default function BasicTableOne() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={closeModal}
|
||||||
|
className="max-w-[700px] p-6 lg:p-10"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col px-2 overflow-y-auto custom-scrollbar">
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-2 font-semibold text-gray-800 modal-title text-theme-xl dark:text-white/90 lg:text-2xl">
|
||||||
|
Tambah Transaksi
|
||||||
|
</h5>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Tambah transaksi baru dan buat invoice
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||||
|
ID Rgeistrasi
|
||||||
|
</label>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{dataModal.IdRegistrasi}
|
||||||
|
</p>
|
||||||
|
{/* <input
|
||||||
|
id="event-title"
|
||||||
|
type="text"
|
||||||
|
|
||||||
|
className="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<label className="block mb-1.5 text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||||
|
Nama
|
||||||
|
</label>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{dataModal.NamaPasien}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<label className="block mb-1.5 text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||||
|
Asuransi / No Asuransi
|
||||||
|
</label>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{dataModal.NamaAsuransi} / {dataModal.NomorKartuAsuransi}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Invoice Items */}
|
||||||
|
<div className="space-y-4 mt-8">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>Invoice Items</Label>
|
||||||
|
<button
|
||||||
|
onClick={addItem}
|
||||||
|
className="text-sm font-medium px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
+ Add Item
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{invoiceItems.map((item, index) => (
|
||||||
|
<div key={index} className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div className="relative">
|
||||||
|
<Select
|
||||||
|
options={actionOption}
|
||||||
|
placeholder="Select an option"
|
||||||
|
onChange={(e) => handleItemChange(index, "description", e)}
|
||||||
|
className="dark:bg-dark-900"
|
||||||
|
/>
|
||||||
|
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Quantity"
|
||||||
|
value={item.quantity}
|
||||||
|
onChange={(e) => handleItemChange(index, "quantity", Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center justify-between space-x-4">
|
||||||
|
{/* Price */}
|
||||||
|
<p className="flex items-center h-11 rounded-md px-4 text-right">
|
||||||
|
Rp {item.price.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Delete Button with Trash Icon */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDeleteRow(index)}
|
||||||
|
className="flex items-center justify-center h-8 w-8 rounded-full bg-red-500 text-white hover:bg-red-600 transition"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="h-5 w-5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
))}
|
||||||
|
<div className="mt-6 flex justify-end">
|
||||||
|
<div className="w-full md:w-1/3">
|
||||||
|
<div className="flex items-center justify-between border-t border-gray-300 pt-4 dark:border-gray-700">
|
||||||
|
<span className="font-semibold">Total:</span>
|
||||||
|
<span className="font-bold">
|
||||||
|
Rp {invoiceItems.reduce((acc, item) => acc + (item.price), 0).toLocaleString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 mt-6 modal-footer sm:justify-end">
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success btn-update-event flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/app/(admin)/invoice/page.tsx
Normal file
7
src/app/(admin)/invoice/page.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import InvoicePage from "@/components/invoice/invoice";
|
||||||
|
|
||||||
|
export default function BasicTableOne() {
|
||||||
|
return(
|
||||||
|
<InvoicePage/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -2,14 +2,96 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ComponentCard from "@/components/common/ComponentCard";
|
import ComponentCard from "@/components/common/ComponentCard";
|
||||||
|
import axios from "axios";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
|
||||||
|
const history = [
|
||||||
|
{
|
||||||
|
eventName: 'Konsultasi dengan Pak Budi',
|
||||||
|
dateTime: '2025-04-28T10:00:00.000Z',
|
||||||
|
status: 'Confirmed',
|
||||||
|
notes: 'Discussing project progress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
eventName: 'Konsultasi dengan Ibu Siti',
|
||||||
|
dateTime: '2025-04-28T14:00:00.000Z',
|
||||||
|
status: 'Pending',
|
||||||
|
notes: 'Discussing business strategy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
eventName: 'Review Project',
|
||||||
|
dateTime: '2025-04-29T09:00:00.000Z',
|
||||||
|
status: 'Confirmed',
|
||||||
|
notes: 'Reviewing the latest designs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
eventName: 'Konsultasi Pajak',
|
||||||
|
dateTime: '2025-04-29T13:00:00.000Z',
|
||||||
|
status: 'Canceled',
|
||||||
|
notes: 'Need to reschedule',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function HistoryPage() {
|
export default function HistoryPage() {
|
||||||
|
|
||||||
|
const [bookings, setBookings] = useState([]);
|
||||||
|
|
||||||
|
const getHistory = async () => {
|
||||||
|
const result = await axios.get("/api/registration")
|
||||||
|
|
||||||
|
setBookings(result.data.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getHistory()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
console.log(bookings)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComponentCard title="History Jadwal">
|
<ComponentCard title="History Jadwal">
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
|
{bookings.map((booking, index) => {
|
||||||
|
// Membuat timestamp dari TanggalRegistrasi dan JamKonsul
|
||||||
|
const bookingTimestamp = dayjs(`${booking.TanggalRegistrasi} ${booking.JamKonsul}`, 'DD MMMM YYYY HH:mm');
|
||||||
|
|
||||||
|
// Membandingkan dengan waktu sekarang
|
||||||
|
const isConfirmed = bookingTimestamp.isBefore(dayjs()); // Jika bookingTimestamp lebih kecil dari waktu sekarang, berarti sudah lewat
|
||||||
|
|
||||||
|
// Menentukan status
|
||||||
|
booking.status = isConfirmed ? 'Confirmed' : 'Pending';
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`p-4 rounded-lg border-2 ${booking.status === 'Confirmed'
|
||||||
|
? 'border-green-500 bg-green-50'
|
||||||
|
: booking.status === 'Pending'
|
||||||
|
? 'border-yellow-500 bg-yellow-50'
|
||||||
|
: 'border-red-500 bg-red-50'
|
||||||
|
} flex justify-between items-center`}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-semibold">{booking.NamaRuangPelayanan} dengan {booking.NamaPegawai}</h3>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
{`${dayjs(booking.TanggalRegistrasi).format('DD MMMM YYYY')} ${booking.JamKonsul}`}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">{booking.NamaAsuransi}: {booking.NomorKartuAsuransi}</p>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={`px-3 py-1 text-white rounded-full ${booking.status === 'Confirmed'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: booking.status === 'Pending'
|
||||||
|
? 'bg-yellow-500'
|
||||||
|
: 'bg-red-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{booking.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
</ComponentCard>
|
||||||
);
|
);
|
||||||
|
|||||||
7
src/app/(user)/invoice-user/page.tsx
Normal file
7
src/app/(user)/invoice-user/page.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import InvoicePage from "@/components/invoice/invoice";
|
||||||
|
|
||||||
|
export default function BasicTableOne() {
|
||||||
|
return(
|
||||||
|
<InvoicePage/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,8 +4,10 @@ import React, { useEffect, useState } from "react";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import TransactionCard from "@/components/card/TransactionCard";
|
import TransactionCard from "@/components/card/TransactionCard";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function TransactionPage() {
|
export default function TransactionPage() {
|
||||||
|
const router = useRouter()
|
||||||
const [transaction, setTransaction] = useState([])
|
const [transaction, setTransaction] = useState([])
|
||||||
|
|
||||||
const getDataTransaction = async () =>{
|
const getDataTransaction = async () =>{
|
||||||
@ -18,7 +20,10 @@ export default function TransactionPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(transaction,"pp")
|
const handleCardClick = () =>{
|
||||||
|
router.push('/invoice-user')
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataTransaction()
|
getDataTransaction()
|
||||||
}, [])
|
}, [])
|
||||||
@ -28,6 +33,11 @@ export default function TransactionPage() {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{transaction.map((item: any, index) => {
|
{transaction.map((item: any, index) => {
|
||||||
return(
|
return(
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleCardClick(item.IdRegistrasi)} // Tindakan klik untuk menuju halaman invoice
|
||||||
|
className="cursor-pointer" // Menambahkan pointer cursor agar terlihat klikabel
|
||||||
|
>
|
||||||
<TransactionCard
|
<TransactionCard
|
||||||
key={index}
|
key={index}
|
||||||
customerName={item.NamaPasien}
|
customerName={item.NamaPasien}
|
||||||
@ -36,6 +46,7 @@ export default function TransactionPage() {
|
|||||||
totalAmount={0}
|
totalAmount={0}
|
||||||
status={"Unpaid"}
|
status={"Unpaid"}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
26
src/app/api/action/route.ts
Normal file
26
src/app/api/action/route.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { NextResponse } from 'next/server'; // Menggunakan NextResponse
|
||||||
|
import { handleGetAction } from '@/database/controllers/actionController';
|
||||||
|
|
||||||
|
// Fungsi untuk menangani permintaan GET
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
// Panggil controller untuk mendapatkan data asuransi
|
||||||
|
const response = await handleGetAction();
|
||||||
|
|
||||||
|
// Mengembalikan response dengan status sukses menggunakan NextResponse
|
||||||
|
return NextResponse.json({
|
||||||
|
status: 'SUCCESS',
|
||||||
|
code: 200,
|
||||||
|
data: response, // Data hasil query
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching insurance:', error);
|
||||||
|
|
||||||
|
// Jika terjadi error, kembalikan status gagal dengan error message
|
||||||
|
return NextResponse.json({
|
||||||
|
status: 'FAILURE',
|
||||||
|
code: 500,
|
||||||
|
message: 'Internal Server Error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ import Label from "@/components/form/Label";
|
|||||||
import Button from "@/components/ui/button/Button";
|
import Button from "@/components/ui/button/Button";
|
||||||
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "@/icons";
|
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "@/icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { useState } from "react";
|
import React, { MouseEvent, useState } from "react";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
@ -18,20 +18,23 @@ export default function SignInForm() {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
|
||||||
const handleSignIn = async () => {
|
const handleSignIn = async (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
// Simulasi login & ambil role dari backend
|
e.preventDefault(); // ← tambahin ini
|
||||||
const role = email === 'admin@example.com' ? 'admin' : 'user'
|
console.log("first");
|
||||||
|
|
||||||
// Simpan role ke cookie
|
const role = email === 'admin@example.com' ? 'admin' : 'user';
|
||||||
// Cookies.set('role', role)
|
|
||||||
|
Cookies.set('role', role);
|
||||||
|
|
||||||
// Redirect ke dashboard
|
|
||||||
if (role === 'admin') {
|
if (role === 'admin') {
|
||||||
return router.push('/')
|
router.push('/all-registration');
|
||||||
|
} else {
|
||||||
|
router.push('/booking');
|
||||||
}
|
}
|
||||||
return router.push('/booking')
|
console.log("role");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 lg:w-1/2 w-full">
|
<div className="flex flex-col flex-1 lg:w-1/2 w-full">
|
||||||
<div className="w-full max-w-md sm:pt-10 mx-auto mb-5">
|
<div className="w-full max-w-md sm:pt-10 mx-auto mb-5">
|
||||||
@ -151,7 +154,7 @@ export default function SignInForm() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button className="w-full" size="sm" onClick={handleSignIn}>
|
<Button className="w-full" size="sm" onClick={(e) => handleSignIn(e)}>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default function TransactionCard({
|
|||||||
totalAmount,
|
totalAmount,
|
||||||
status,
|
status,
|
||||||
}: TransactionCardProps) {
|
}: TransactionCardProps) {
|
||||||
console.log( dayjs(date).add(7, 'hour').format("DD MMMM YYYY HH:mm") ,"pp")
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-md flex flex-col gap-2 hover:shadow-lg transition">
|
<div className="bg-white p-6 rounded-lg shadow-md flex flex-col gap-2 hover:shadow-lg transition">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
|
|||||||
@ -7,13 +7,13 @@ export default function InvoicePage() {
|
|||||||
invoiceNumber: "INV-2025-001",
|
invoiceNumber: "INV-2025-001",
|
||||||
invoiceDate: "2025-04-27",
|
invoiceDate: "2025-04-27",
|
||||||
dueDate: "2025-05-10",
|
dueDate: "2025-05-10",
|
||||||
customerName: "Darine Nadeva",
|
customerName: "Pasien A",
|
||||||
items: [
|
items: [
|
||||||
{ description: "Website Development", quantity: 1, price: 5000000 },
|
{ description: "Infus", quantity: 1, price: 200000 },
|
||||||
{ description: "Maintenance Support", quantity: 2, price: 750000 },
|
{ description: "Rawat Inap", quantity: 2, price: 750000 },
|
||||||
],
|
],
|
||||||
companyName: "Lifetime Design",
|
companyName: "Rumah Sakit Ibu dan Anak Harapan Kita",
|
||||||
companyAddress: "Jl. Sudirman No. 99, Jakarta",
|
companyAddress: "Jl. Letjen S. Parman No.Kav. 87, Slipi",
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalAmount = invoiceData.items.reduce(
|
const totalAmount = invoiceData.items.reduce(
|
||||||
|
|||||||
15
src/database/controllers/actionController.ts
Normal file
15
src/database/controllers/actionController.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { getAllAction } from '../models/actionModel';
|
||||||
|
|
||||||
|
// Fungsi untuk mengambil data asuransi
|
||||||
|
export async function handleGetAction() {
|
||||||
|
try {
|
||||||
|
// Ambil data asuransi dari model
|
||||||
|
const actions = await getAllAction();
|
||||||
|
|
||||||
|
// Mengembalikan data asuransi
|
||||||
|
return actions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching insurance:', error);
|
||||||
|
throw new Error('Database query failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/database/models/actionModel.ts
Normal file
11
src/database/models/actionModel.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { queryDb } from '../lib/db';
|
||||||
|
export async function getAllAction() {
|
||||||
|
try {
|
||||||
|
const res = await queryDb(`SELECT * FROM "MsTindakan"`);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Database query error:", error);
|
||||||
|
throw new Error('Database query failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -43,3 +43,52 @@ export async function postRegister(payload: RegisterPayload) {
|
|||||||
throw new Error('Database insert failed');
|
throw new Error('Database insert failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface RegistrasiData {
|
||||||
|
NomorKartuAsuransi: number;
|
||||||
|
MRPasien: number;
|
||||||
|
IdAsuransi: number;
|
||||||
|
IdRuangPelayanan: number;
|
||||||
|
TanggalKonsul: string;
|
||||||
|
JamKonsul: string;
|
||||||
|
TanggalRegistrasi: string;
|
||||||
|
NamaPasien: string;
|
||||||
|
NamaAsuransi: string;
|
||||||
|
NamaPegawai: string;
|
||||||
|
NamaRuangPelayanan: string;
|
||||||
|
IdRegistrasi: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model untuk mengambil data Registrasi
|
||||||
|
export async function getRegistrasiData(): Promise<RegistrasiData[]> {
|
||||||
|
try {
|
||||||
|
const result = await queryDb(`
|
||||||
|
SELECT
|
||||||
|
tr."NomorKartuAsuransi",
|
||||||
|
tr."MRPasien",
|
||||||
|
tr."IdAsuransi",
|
||||||
|
tr."IdRuangPelayanan",
|
||||||
|
tr."TanggalKonsul",
|
||||||
|
tr."JamKonsul",
|
||||||
|
tr."TanggalRegistrasi",
|
||||||
|
mp."NamaPasien",
|
||||||
|
ma."NamaAsuransi",
|
||||||
|
mpgw."NamaPegawai",
|
||||||
|
mrp."NamaRuangPelayanan",
|
||||||
|
tr."IdRegistrasi"
|
||||||
|
FROM "TrRegistrasi" tr
|
||||||
|
JOIN "MsPasien" mp ON tr."MRPasien" = mp."MRPasien"
|
||||||
|
JOIN "MsAsuransi" ma ON tr."IdAsuransi" = ma."IdAsuransi"
|
||||||
|
JOIN "MsPegawai" mpgw ON tr."IdPegawai" = mpgw."IdPegawai"
|
||||||
|
JOIN "MsRuangPelayanan" mrp ON tr."IdRuangPelayanan" = mrp."IdRuangPelayanan"
|
||||||
|
WHERE tr."MRPasien" = mp."MRPasien"
|
||||||
|
ORDER BY tr."IdRegistrasi" ASC;
|
||||||
|
`);
|
||||||
|
|
||||||
|
return result; // Mengembalikan data hasil query
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching registrasi data:", error);
|
||||||
|
throw new Error('Database query failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,14 +40,6 @@ const navItems: NavItem[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const othersItems: NavItem[] = [
|
const othersItems: NavItem[] = [
|
||||||
{
|
|
||||||
icon: <PieChartIcon />,
|
|
||||||
name: "Charts",
|
|
||||||
subItems: [
|
|
||||||
{ name: "Line Chart", path: "/line-chart", pro: false },
|
|
||||||
{ name: "Bar Chart", path: "/bar-chart", pro: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const AppSidebar: React.FC = () => {
|
const AppSidebar: React.FC = () => {
|
||||||
@ -56,7 +48,7 @@ const AppSidebar: React.FC = () => {
|
|||||||
|
|
||||||
const renderMenuItems = (
|
const renderMenuItems = (
|
||||||
navItems: NavItem[],
|
navItems: NavItem[],
|
||||||
menuType: "main" | "others"
|
menuType: "main"
|
||||||
) => (
|
) => (
|
||||||
<ul className="flex flex-col gap-4">
|
<ul className="flex flex-col gap-4">
|
||||||
{navItems.map((nav, index) => (
|
{navItems.map((nav, index) => (
|
||||||
@ -181,7 +173,7 @@ const AppSidebar: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [openSubmenu, setOpenSubmenu] = useState<{
|
const [openSubmenu, setOpenSubmenu] = useState<{
|
||||||
type: "main" | "others";
|
type: "main";
|
||||||
index: number;
|
index: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>(
|
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>(
|
||||||
@ -195,14 +187,14 @@ const AppSidebar: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if the current path matches any submenu item
|
// Check if the current path matches any submenu item
|
||||||
let submenuMatched = false;
|
let submenuMatched = false;
|
||||||
["main", "others"].forEach((menuType) => {
|
["main"].forEach((menuType) => {
|
||||||
const items = menuType === "main" ? navItems : othersItems;
|
const items = menuType === "main" ? navItems : othersItems;
|
||||||
items.forEach((nav, index) => {
|
items.forEach((nav, index) => {
|
||||||
if (nav.subItems) {
|
if (nav.subItems) {
|
||||||
nav.subItems.forEach((subItem) => {
|
nav.subItems.forEach((subItem) => {
|
||||||
if (isActive(subItem.path)) {
|
if (isActive(subItem.path)) {
|
||||||
setOpenSubmenu({
|
setOpenSubmenu({
|
||||||
type: menuType as "main" | "others",
|
type: menuType as "main",
|
||||||
index,
|
index,
|
||||||
});
|
});
|
||||||
submenuMatched = true;
|
submenuMatched = true;
|
||||||
@ -231,7 +223,7 @@ const AppSidebar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [openSubmenu]);
|
}, [openSubmenu]);
|
||||||
|
|
||||||
const handleSubmenuToggle = (index: number, menuType: "main" | "others") => {
|
const handleSubmenuToggle = (index: number, menuType: "main") => {
|
||||||
setOpenSubmenu((prevOpenSubmenu) => {
|
setOpenSubmenu((prevOpenSubmenu) => {
|
||||||
if (
|
if (
|
||||||
prevOpenSubmenu &&
|
prevOpenSubmenu &&
|
||||||
@ -312,22 +304,7 @@ const AppSidebar: React.FC = () => {
|
|||||||
{renderMenuItems(navItems, "main")}
|
{renderMenuItems(navItems, "main")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="">
|
|
||||||
<h2
|
|
||||||
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${
|
|
||||||
!isExpanded && !isHovered
|
|
||||||
? "lg:justify-center"
|
|
||||||
: "justify-start"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{isExpanded || isHovered || isMobileOpen ? (
|
|
||||||
"Others"
|
|
||||||
) : (
|
|
||||||
<HorizontaLDots />
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
{renderMenuItems(othersItems, "others")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -22,6 +22,6 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/types/app/(user)/transactions/page.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user