mirror of
				https://github.com/i701/sarlink-portal.git
				synced 2025-10-31 16:07:00 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										125
									
								
								app/(dashboard)/price-calculator/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								app/(dashboard)/price-calculator/page.tsx
									
									
									
									
									
										Normal 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> | ||||
|   ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user