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 axios from "axios";
|
||||
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 {
|
||||
@ -32,18 +40,113 @@ interface Item {
|
||||
|
||||
|
||||
export default function BasicTableOne() {
|
||||
const [registration, setRegistration] = useState([])
|
||||
|
||||
const getAllRegistration = async () =>{
|
||||
const result = await axios.get("/api/registration")
|
||||
const router = useRouter()
|
||||
|
||||
setRegistration(result.data.data)
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
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 addItem = () => {
|
||||
setInvoiceItems([...invoiceItems, { description: "", quantity: 1, price: 0 }]);
|
||||
};
|
||||
|
||||
|
||||
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(() => {
|
||||
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 (
|
||||
<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">
|
||||
@ -62,13 +165,13 @@ export default function BasicTableOne() {
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Nama
|
||||
Nama
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Ruang Pelayanan
|
||||
Ruang Pelayanan
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
@ -129,9 +232,9 @@ export default function BasicTableOne() {
|
||||
{item.NamaPegawai}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-start text-theme-sm dark:text-gray-400">
|
||||
{`${dayjs(item.TanggalRegistrasi).format('DD MMMM YYYY')} ${item.JamKonsul}`}
|
||||
{`${dayjs(item.TanggalRegistrasi).format('DD MMMM YYYY')} ${item.JamKonsul}`}
|
||||
</TableCell>
|
||||
|
||||
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-start text-theme-sm dark:text-gray-400">
|
||||
<Badge
|
||||
size="sm"
|
||||
@ -147,7 +250,9 @@ export default function BasicTableOne() {
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<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>
|
||||
</TableRow>
|
||||
))}
|
||||
@ -155,6 +260,152 @@ export default function BasicTableOne() {
|
||||
</Table>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
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 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() {
|
||||
|
||||
const [bookings, setBookings] = useState([]);
|
||||
|
||||
const getHistory = async () => {
|
||||
const result = await axios.get("/api/registration")
|
||||
|
||||
setBookings(result.data.data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getHistory()
|
||||
}, [])
|
||||
|
||||
console.log(bookings)
|
||||
|
||||
return (
|
||||
<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>
|
||||
</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 TransactionCard from "@/components/card/TransactionCard";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function TransactionPage() {
|
||||
const router = useRouter()
|
||||
const [transaction, setTransaction] = useState([])
|
||||
|
||||
const getDataTransaction = async () =>{
|
||||
@ -18,7 +20,10 @@ export default function TransactionPage() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(transaction,"pp")
|
||||
const handleCardClick = () =>{
|
||||
router.push('/invoice-user')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getDataTransaction()
|
||||
}, [])
|
||||
@ -28,6 +33,11 @@ export default function TransactionPage() {
|
||||
<div className="space-y-6">
|
||||
{transaction.map((item: any, index) => {
|
||||
return(
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => handleCardClick(item.IdRegistrasi)} // Tindakan klik untuk menuju halaman invoice
|
||||
className="cursor-pointer" // Menambahkan pointer cursor agar terlihat klikabel
|
||||
>
|
||||
<TransactionCard
|
||||
key={index}
|
||||
customerName={item.NamaPasien}
|
||||
@ -36,6 +46,7 @@ export default function TransactionPage() {
|
||||
totalAmount={0}
|
||||
status={"Unpaid"}
|
||||
/>
|
||||
</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 { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "@/icons";
|
||||
import Link from "next/link";
|
||||
import React, { useState } from "react";
|
||||
import React, { MouseEvent, useState } from "react";
|
||||
import Cookies from "js-cookie";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@ -18,20 +18,23 @@ export default function SignInForm() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
|
||||
const handleSignIn = async () => {
|
||||
// Simulasi login & ambil role dari backend
|
||||
const role = email === 'admin@example.com' ? 'admin' : 'user'
|
||||
const handleSignIn = async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault(); // ← tambahin ini
|
||||
console.log("first");
|
||||
|
||||
// Simpan role ke cookie
|
||||
// Cookies.set('role', role)
|
||||
const role = email === 'admin@example.com' ? 'admin' : 'user';
|
||||
|
||||
Cookies.set('role', role);
|
||||
|
||||
// Redirect ke dashboard
|
||||
if (role === 'admin') {
|
||||
return router.push('/')
|
||||
router.push('/all-registration');
|
||||
} else {
|
||||
router.push('/booking');
|
||||
}
|
||||
return router.push('/booking')
|
||||
console.log("role");
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<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">
|
||||
@ -151,7 +154,7 @@ export default function SignInForm() {
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Button className="w-full" size="sm" onClick={handleSignIn}>
|
||||
<Button className="w-full" size="sm" onClick={(e) => handleSignIn(e)}>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -16,7 +16,7 @@ export default function TransactionCard({
|
||||
totalAmount,
|
||||
status,
|
||||
}: TransactionCardProps) {
|
||||
console.log( dayjs(date).add(7, 'hour').format("DD MMMM YYYY HH:mm") ,"pp")
|
||||
|
||||
return (
|
||||
<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">
|
||||
|
||||
@ -7,13 +7,13 @@ export default function InvoicePage() {
|
||||
invoiceNumber: "INV-2025-001",
|
||||
invoiceDate: "2025-04-27",
|
||||
dueDate: "2025-05-10",
|
||||
customerName: "Darine Nadeva",
|
||||
customerName: "Pasien A",
|
||||
items: [
|
||||
{ description: "Website Development", quantity: 1, price: 5000000 },
|
||||
{ description: "Maintenance Support", quantity: 2, price: 750000 },
|
||||
{ description: "Infus", quantity: 1, price: 200000 },
|
||||
{ description: "Rawat Inap", quantity: 2, price: 750000 },
|
||||
],
|
||||
companyName: "Lifetime Design",
|
||||
companyAddress: "Jl. Sudirman No. 99, Jakarta",
|
||||
companyName: "Rumah Sakit Ibu dan Anak Harapan Kita",
|
||||
companyAddress: "Jl. Letjen S. Parman No.Kav. 87, Slipi",
|
||||
};
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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[] = [
|
||||
{
|
||||
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 = () => {
|
||||
@ -56,7 +48,7 @@ const AppSidebar: React.FC = () => {
|
||||
|
||||
const renderMenuItems = (
|
||||
navItems: NavItem[],
|
||||
menuType: "main" | "others"
|
||||
menuType: "main"
|
||||
) => (
|
||||
<ul className="flex flex-col gap-4">
|
||||
{navItems.map((nav, index) => (
|
||||
@ -181,7 +173,7 @@ const AppSidebar: React.FC = () => {
|
||||
);
|
||||
|
||||
const [openSubmenu, setOpenSubmenu] = useState<{
|
||||
type: "main" | "others";
|
||||
type: "main";
|
||||
index: number;
|
||||
} | null>(null);
|
||||
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>(
|
||||
@ -195,14 +187,14 @@ const AppSidebar: React.FC = () => {
|
||||
useEffect(() => {
|
||||
// Check if the current path matches any submenu item
|
||||
let submenuMatched = false;
|
||||
["main", "others"].forEach((menuType) => {
|
||||
["main"].forEach((menuType) => {
|
||||
const items = menuType === "main" ? navItems : othersItems;
|
||||
items.forEach((nav, index) => {
|
||||
if (nav.subItems) {
|
||||
nav.subItems.forEach((subItem) => {
|
||||
if (isActive(subItem.path)) {
|
||||
setOpenSubmenu({
|
||||
type: menuType as "main" | "others",
|
||||
type: menuType as "main",
|
||||
index,
|
||||
});
|
||||
submenuMatched = true;
|
||||
@ -231,7 +223,7 @@ const AppSidebar: React.FC = () => {
|
||||
}
|
||||
}, [openSubmenu]);
|
||||
|
||||
const handleSubmenuToggle = (index: number, menuType: "main" | "others") => {
|
||||
const handleSubmenuToggle = (index: number, menuType: "main") => {
|
||||
setOpenSubmenu((prevOpenSubmenu) => {
|
||||
if (
|
||||
prevOpenSubmenu &&
|
||||
@ -312,22 +304,7 @@ const AppSidebar: React.FC = () => {
|
||||
{renderMenuItems(navItems, "main")}
|
||||
</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>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@ -22,6 +22,6 @@
|
||||
"@/*": ["./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"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user