summaryrefslogtreecommitdiff
path: root/lib/users/access-control/domain-stats-cards.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/users/access-control/domain-stats-cards.tsx')
-rw-r--r--lib/users/access-control/domain-stats-cards.tsx232
1 files changed, 232 insertions, 0 deletions
diff --git a/lib/users/access-control/domain-stats-cards.tsx b/lib/users/access-control/domain-stats-cards.tsx
new file mode 100644
index 00000000..e6320e12
--- /dev/null
+++ b/lib/users/access-control/domain-stats-cards.tsx
@@ -0,0 +1,232 @@
+// components/domain-stats-cards.tsx
+"use client"
+
+import * as React from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+ Users,
+ Clock,
+ Shield,
+ ShoppingCart,
+ TrendingUp,
+ Settings,
+ Building,
+ AlertCircle
+} from "lucide-react"
+import { toast } from "sonner"
+import { getUserDomainStats } from "../service"
+
+interface DomainStatsCardsProps {
+ onDomainFilter: (domain: string | null) => void
+ currentFilter?: string | null
+}
+
+// 도메인별 설정
+const domainConfig = {
+ pending: {
+ label: "승인 대기",
+ description: "신규 사용자",
+ icon: Clock,
+ color: "bg-yellow-500",
+ textColor: "text-yellow-700",
+ bgColor: "bg-yellow-50",
+ borderColor: "border-yellow-200"
+ },
+ evcp: {
+ label: "전체 시스템",
+ description: "관리자급",
+ icon: Shield,
+ color: "bg-blue-500",
+ textColor: "text-blue-700",
+ bgColor: "bg-blue-50",
+ borderColor: "border-blue-200"
+ },
+ procurement: {
+ label: "구매관리팀",
+ description: "구매/계약 관리",
+ icon: ShoppingCart,
+ color: "bg-green-500",
+ textColor: "text-green-700",
+ bgColor: "bg-green-50",
+ borderColor: "border-green-200"
+ },
+ sales: {
+ label: "기술영업팀",
+ description: "영업/프로젝트",
+ icon: TrendingUp,
+ color: "bg-purple-500",
+ textColor: "text-purple-700",
+ bgColor: "bg-purple-50",
+ borderColor: "border-purple-200"
+ },
+ engineering: {
+ label: "설계관리팀",
+ description: "설계/기술평가",
+ icon: Settings,
+ color: "bg-orange-500",
+ textColor: "text-orange-700",
+ bgColor: "bg-orange-50",
+ borderColor: "border-orange-200"
+ },
+ // partners: {
+ // label: "협력업체",
+ // description: "외부 업체",
+ // icon: Building,
+ // color: "bg-indigo-500",
+ // textColor: "text-indigo-700",
+ // bgColor: "bg-indigo-50",
+ // borderColor: "border-indigo-200"
+ // }
+}
+
+interface DomainStats {
+ domain: string
+ count: number
+}
+
+export function DomainStatsCards({ onDomainFilter, currentFilter }: DomainStatsCardsProps) {
+ const [stats, setStats] = React.useState<DomainStats[]>([])
+ const [isLoading, setIsLoading] = React.useState(true)
+ const [totalUsers, setTotalUsers] = React.useState(0)
+
+ // 통계 데이터 로드
+ React.useEffect(() => {
+ const loadStats = async () => {
+ setIsLoading(true)
+ try {
+ const result = await getUserDomainStats()
+ if (result.success) {
+ setStats(result.data)
+ setTotalUsers(result.data.reduce((sum, item) => sum + item.count, 0))
+ } else {
+ toast.error(result.message)
+ }
+ } catch (error) {
+ console.error("통계 로드 오류:", error)
+ toast.error("통계를 불러오는 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadStats()
+ }, [])
+
+ // 도메인별 카드 클릭 핸들러
+ const handleDomainClick = (domain: string) => {
+ if (currentFilter === domain) {
+ // 이미 선택된 도메인이면 필터 해제
+ onDomainFilter(null)
+ } else {
+ // 새로운 도메인 필터 적용
+ onDomainFilter(domain)
+ }
+ }
+
+ // pending 사용자 수 가져오기
+ const pendingCount = stats.find(s => s.domain === "pending")?.count || 0
+
+ if (isLoading) {
+ return (
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
+ {Array.from({ length: 6 }).map((_, i) => (
+ <Card key={i} className="animate-pulse">
+ <CardContent className="p-4">
+ <div className="h-16 bg-gray-200 rounded"></div>
+ </CardContent>
+ </Card>
+ ))}
+ </div>
+ )
+ }
+
+ return (
+ <div className="space-y-4">
+ {/* 요약 정보 */}
+ <div className="flex items-center justify-between">
+ <div>
+ <h3 className="text-lg font-semibold">도메인별 사용자 현황</h3>
+ <p className="text-sm text-muted-foreground">
+ 총 {totalUsers}명의 사용자가 등록되어 있습니다.
+ </p>
+ </div>
+
+ {pendingCount > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => handleDomainClick("pending")}
+ className={`gap-2 ${currentFilter === "pending" ? "bg-yellow-50 border-yellow-300" : ""}`}
+ >
+ <AlertCircle className="h-4 w-4 text-yellow-600" />
+ 승인 대기 {pendingCount}명
+ </Button>
+ )}
+ </div>
+
+ {/* 도메인별 통계 카드 */}
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
+ {Object.entries(domainConfig).map(([domain, config]) => {
+ const domainStat = stats.find(s => s.domain === domain)
+ const count = domainStat?.count || 0
+ const isActive = currentFilter === domain
+ const IconComponent = config.icon
+
+ return (
+ <Card
+ key={domain}
+ className={`cursor-pointer transition-all hover:shadow-md ${
+ isActive
+ ? `${config.bgColor} ${config.borderColor} border-2`
+ : "hover:bg-gray-50"
+ }`}
+ onClick={() => handleDomainClick(domain)}
+ >
+ <CardContent className="p-4">
+ <div className="flex items-center space-x-3">
+ <div className={`p-2 rounded-full ${config.color}`}>
+ <IconComponent className="h-4 w-4 text-white" />
+ </div>
+ <div className="flex-1 min-w-0">
+ <div className="flex items-center justify-between">
+ <p className={`text-sm font-medium ${isActive ? config.textColor : "text-gray-900"}`}>
+ {config.label}
+ </p>
+ <Badge
+ variant={isActive ? "default" : "secondary"}
+ className="ml-2"
+ >
+ {count}
+ </Badge>
+ </div>
+ <p className="text-xs text-muted-foreground truncate">
+ {config.description}
+ </p>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ )
+ })}
+ </div>
+
+ {/* 필터 상태 표시 */}
+ {currentFilter && (
+ <div className="flex items-center gap-2">
+ <span className="text-sm text-muted-foreground">필터 적용됨:</span>
+ <Badge variant="outline" className="gap-1">
+ {domainConfig[currentFilter as keyof typeof domainConfig]?.label || currentFilter}
+ <button
+ onClick={() => onDomainFilter(null)}
+ className="ml-1 hover:bg-gray-200 rounded-full p-0.5"
+ >
+ ×
+ </button>
+ </Badge>
+ </div>
+ )}
+ </div>
+ )
+} \ No newline at end of file