Enhance dashboard functionality with new payment and device management features

- Added new PaymentPage component for processing payments and displaying devices to pay.
- Introduced DeviceDetails component for viewing individual device information.
- Implemented PriceCalculator component for calculating costs based on user input.
- Integrated Jotai for state management across components, including device cart functionality.
- Updated layout to include Jotai Provider for state management.
- Enhanced DevicesTable with AddDevicesToCartButton for adding devices to the cart.
- Refactored sidebar to include a link to the new Price Calculator page.
- Updated Prisma schema to include Payment and BillFormula models for better data handling.
- Added new UI components for device cart management and drawer functionality.
- Improved overall user experience with responsive design adjustments and new UI elements.
This commit is contained in:
2024-12-06 14:16:05 +05:00
parent 9021f01ff4
commit c6f45710ca
23 changed files with 2545 additions and 50 deletions

View File

@ -0,0 +1,39 @@
import prisma from '@/lib/db'
import React from 'react'
export default async function DeviceDetails({ params }: {
params: Promise<{ deviceId: string }>
}) {
const deviceId = (await params)?.deviceId
const device = await prisma.device.findUnique({
where: {
id: deviceId,
},
})
return (
<div>
<div className="flex flex-col justify-between items-start border-b-2 text-gray-500 title-bg py-4 px-2 mb-4">
<h3 className='text-2xl font-bold'>
{device?.name}
</h3>
<span>{device?.mac}</span>
</div>
<div
id="user-filters"
className=" border-b-2 pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
>
{/* <Search /> */}
{/* <Filter
options={sortfilterOptions}
defaultOption="asc"
queryParamKey="sortBy"
/> */}
</div>
{/* <Suspense key={query} fallback={"loading...."}>
<DevicesTable searchParams={searchParams} />
</Suspense> */}
</div>
)
}

View File

@ -43,7 +43,7 @@ export default async function Devices({
<div
id="user-filters"
className=" border-b-2 pb-4 gap-4 flex items-center justify-start"
className=" border-b-2 pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
>
<Search />
<Filter

View File

@ -0,0 +1,24 @@
import DevicesToPay from '@/components/devices-to-pay';
import prisma from '@/lib/db';
import React from 'react'
export default async function PaymentPage() {
const formula = await prisma.billFormula.findFirst();
return (
<div>
<div className="flex justify-between items-center border-b-2 text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
<h3>
Payment
</h3>
</div>
<div
id="user-filters"
className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
>
<DevicesToPay billFormula={formula ?? undefined} />
</div>
</div>
)
}

View File

@ -0,0 +1,125 @@
"use client";
import {
discountPercentageAtom,
formulaResultAtom,
initialPriceAtom,
numberOfDaysAtom,
numberOfDevicesAtom,
} from "@/lib/atoms";
import { useAtom } from "jotai";
import { Minus, Plus } from "lucide-react";
import { useEffect } from "react";
import {
Button,
Group,
Input,
Label,
NumberField,
} from "react-aria-components";
export default function PriceCalculator() {
const [initialPrice, setInitialPrice] = useAtom(initialPriceAtom);
const [discountPercentage, setDiscountPercentage] = useAtom(
discountPercentageAtom,
);
const [numberOfDevices, setNumberOfDevices] = useAtom(numberOfDevicesAtom);
const [numberOfDays, setNumberOfDays] = useAtom(numberOfDaysAtom);
const [formulaResult, setFormulaResult] = useAtom(formulaResultAtom);
useEffect(() => {
const basePrice = initialPrice + (numberOfDevices - 1) * discountPercentage;
setFormulaResult(
`Price for ${numberOfDevices} device(s) over ${numberOfDays} day(s): MVR ${basePrice.toFixed(2)}`,
);
}, [
initialPrice,
discountPercentage,
numberOfDevices,
numberOfDays,
setFormulaResult,
]);
return (
<div className="border p-2 rounded-xl">
<div className="flex flex-col justify-between items-start text-gray-500 title-bg p-2 mb-4">
<h3 className="text-2xl font-semibold">Price Calculator</h3>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* Initial Price Input */}
<NumberInput
label="Initial Price"
value={initialPrice}
onChange={(value) => setInitialPrice(value)}
/>
{/* Number of Devices Input */}
<NumberInput
label="Number of Devices"
value={numberOfDevices}
onChange={(value) => setNumberOfDevices(value)}
/>
{/* Number of Days Input */}
<NumberInput
label="Number of Days"
value={numberOfDays}
onChange={(value) => setNumberOfDays(value)}
/>
{/* Discount Percentage Input */}
<NumberInput
label="Discount Percentage"
value={discountPercentage}
onChange={(value) => setDiscountPercentage(value)}
/>
</div>
<div className="mt-4">
<div className="title-bg relative rounded-lg border border-input shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 [&:has(input:is(:disabled))_*]:pointer-events-none">
<label
htmlFor=""
className="block px-3 pt-2 text-md font-medium text-foreground"
>
Total
</label>
<input
className="flex font-mono font-semibold h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
value={formulaResult}
readOnly
placeholder={"Result"}
/>
</div>
</div>
</div>
);
}
// Dependencies: pnpm install lucide-react react-aria-components
function NumberInput({
label,
value,
onChange,
}: { label: string; value: number; onChange: (value: number) => void }) {
return (
<NumberField value={value} minValue={0} onChange={onChange}>
<div className="space-y-2">
<Label className="text-sm font-medium text-foreground">{label}</Label>
<Group className="relative inline-flex h-9 w-full items-center overflow-hidden whitespace-nowrap rounded-lg border border-input text-sm shadow-sm shadow-black/5 transition-shadow data-[focus-within]:border-ring data-[disabled]:opacity-50 data-[focus-within]:outline-none data-[focus-within]:ring-[3px] data-[focus-within]:ring-ring/20">
<Button
slot="decrement"
className="-ms-px flex aspect-square h-[inherit] items-center justify-center rounded-s-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
>
<Minus size={16} strokeWidth={2} aria-hidden="true" />
</Button>
<Input className="w-full grow bg-background px-3 py-2 text-center tabular-nums text-foreground focus:outline-none" />
<Button
slot="increment"
className="-me-px flex aspect-square h-[inherit] items-center justify-center rounded-e-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
>
<Plus size={16} strokeWidth={2} aria-hidden="true" />
</Button>
</Group>
</div>
</NumberField>
);
}

View File

@ -1,4 +1,5 @@
import { ThemeProvider } from "@/components/theme-provider";
import { Provider } from "jotai";
import type { Metadata } from "next";
import { Barlow } from "next/font/google";
@ -25,16 +26,18 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className={`${barlow.variable} antialiased font-sans`}>
<NextTopLoader showSpinner={false} zIndex={9999} />
<Toaster richColors />
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<QueryProvider>{children}</QueryProvider>
</ThemeProvider>
<Provider>
<NextTopLoader showSpinner={false} zIndex={9999} />
<Toaster richColors />
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<QueryProvider>{children}</QueryProvider>
</ThemeProvider>
</Provider>
</body>
</html>
);