order detail

This commit is contained in:
hamid zarghami
2025-08-26 15:58:24 +03:30
parent 90bf32f8fd
commit 09a210c943
6 changed files with 634 additions and 3 deletions
+65
View File
@@ -0,0 +1,65 @@
'use client'
import { FC, ReactNode, useEffect } from 'react'
import { CloseCircle } from 'iconsax-react'
import { cn } from '@/lib/utils'
type Props = {
open: boolean
close: () => void
children: ReactNode
title?: string
isHeader?: boolean
className?: string
}
const Modal: FC<Props> = ({ open, close, children, title, isHeader = false, className }) => {
useEffect(() => {
if (open) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'unset'
}
return () => {
document.body.style.overflow = 'unset'
}
}, [open])
if (!open) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
onClick={close}
/>
{/* Modal */}
<div className={cn(
"relative bg-white rounded-2xl shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto",
className
)}>
{/* Header */}
{isHeader && (
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">{title}</h2>
<button
onClick={close}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<CloseCircle size={20} />
</button>
</div>
)}
{/* Content */}
<div className="p-6">
{children}
</div>
</div>
</div>
)
}
export default Modal
+15
View File
@@ -0,0 +1,15 @@
'use client'
import { FC } from 'react'
const PageLoading: FC = () => {
return (
<div className="flex items-center justify-center py-20">
<div className="flex flex-col items-center gap-4">
<div className="size-10 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div>
<p className="text-gray-600">در حال بارگذاری...</p>
</div>
</div>
)
}
export default PageLoading
+114
View File
@@ -0,0 +1,114 @@
'use client'
import { FC, useState, useRef, useEffect } from 'react'
import { ArrowDown2 } from 'iconsax-react'
import { cn } from '@/lib/utils'
type SelectItem = {
label: string
value: string
}
type Props = {
placeholder?: string
items?: SelectItem[]
value?: string
name?: string
onChange?: (e: { target: { name: string; value: string } }) => void
className?: string
error_text?: string
disabled?: boolean
}
const Select: FC<Props> = ({
placeholder,
items = [],
value,
name,
onChange,
className,
error_text,
disabled = false
}) => {
const [isOpen, setIsOpen] = useState(false)
const [selectedItem, setSelectedItem] = useState<SelectItem | null>(
items.find(item => item.value === value) || null
)
const dropdownRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
useEffect(() => {
const item = items.find(item => item.value === value)
setSelectedItem(item || null)
}, [value, items])
const handleSelect = (item: SelectItem) => {
setSelectedItem(item)
setIsOpen(false)
if (onChange && name) {
onChange({
target: {
name,
value: item.value
}
})
}
}
return (
<div className="w-full relative" ref={dropdownRef}>
<div
className={cn(
"w-full h-10 px-4 py-2 text-sm border border-[#D0D0D0] rounded-xl bg-white cursor-pointer flex items-center justify-between",
disabled && "bg-gray-100 cursor-not-allowed",
error_text && "border-red-500",
className
)}
onClick={() => !disabled && setIsOpen(!isOpen)}
>
<span className={selectedItem ? "text-black" : "text-gray-500"}>
{selectedItem ? selectedItem.label : placeholder}
</span>
<ArrowDown2
size={16}
className={cn(
"transition-transform duration-200",
isOpen && "rotate-180"
)}
/>
</div>
{isOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-white border border-[#D0D0D0] rounded-xl shadow-lg z-50 max-h-60 overflow-y-auto">
{items.map((item, index) => (
<div
key={index}
className="px-4 py-2 hover:bg-gray-50 cursor-pointer text-sm"
onClick={() => handleSelect(item)}
>
{item.label}
</div>
))}
</div>
)}
{error_text && (
<div className="text-xs text-red-500 mt-1">
{error_text}
</div>
)}
</div>
)
}
export default Select
+2 -3
View File
@@ -1,12 +1,11 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { ClipLoader } from "react-spinners"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive",
{
variants: {
variant: {
@@ -61,7 +60,7 @@ function Button({
>
{isLoading ? (
<>
<ClipLoader size={16} color="currentColor" />
<div className="size-4 border-2 border-current/20 border-t-current rounded-full animate-spin"></div>
<span>در حال بارگذاری...</span>
</>
) : (