Change Directory For Charts

This commit is contained in:
Randa Firman Putra
2025-06-29 10:30:14 +07:00
parent 37d083ec31
commit 6941b8bb8d
45 changed files with 276 additions and 257 deletions

View File

@@ -3,14 +3,14 @@
import { useState } from "react"; import { useState } from "react";
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan"; import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
import FilterJenisBeasiswa from "@/components/FilterJenisBeasiswa"; import FilterJenisBeasiswa from "@/components/FilterJenisBeasiswa";
import TotalBeasiswaChart from "@/components/TotalBeasiswaChart"; import TotalBeasiswaChart from "@/components/charts/TotalBeasiswaChart";
import TotalBeasiswaPieChart from "@/components/TotalBeasiswaPieChart"; import TotalBeasiswaPieChart from "@/components/charts/TotalBeasiswaPieChart";
import NamaBeasiswaChart from "@/components/NamaBeasiswaChart"; import NamaBeasiswaChart from "@/components/charts/NamaBeasiswaChart";
import NamaBeasiswaPieChart from "@/components/NamaBeasiswaPieChart"; import NamaBeasiswaPieChart from "@/components/charts/NamaBeasiswaPieChart";
import JenisPendaftaranBeasiswaChart from "@/components/JenisPendaftaranBeasiswaChart"; import JenisPendaftaranBeasiswaChart from "@/components/charts/JenisPendaftaranBeasiswaChart";
import JenisPendaftaranBeasiswaPieChart from "@/components/JenisPendaftaranBeasiswaPieChart"; import JenisPendaftaranBeasiswaPieChart from "@/components/charts/JenisPendaftaranBeasiswaPieChart";
import AsalDaerahBeasiswaChart from "@/components/AsalDaerahBeasiswaChart"; import AsalDaerahBeasiswaChart from "@/components/charts/AsalDaerahBeasiswaChart";
import IPKBeasiswaChart from "@/components/IPKBeasiswaChart"; import IPKBeasiswaChart from "@/components/charts/IPKBeasiswaChart";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function BeasiswaMahasiswaPage() { export default function BeasiswaMahasiswaPage() {

View File

@@ -4,14 +4,14 @@ import { useState } from "react";
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan"; import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
import FilterJenisPrestasi from "@/components/FilterJenisPrestasi"; import FilterJenisPrestasi from "@/components/FilterJenisPrestasi";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import TotalPrestasiChart from "@/components/TotalPrestasiChart"; import TotalPrestasiChart from "@/components/charts/TotalPrestasiChart";
import TotalPrestasiPieChart from "@/components/TotalPrestasiPieChart"; import TotalPrestasiPieChart from "@/components/charts/TotalPrestasiPieChart";
import TingkatPrestasiChart from "@/components/TingkatPrestasiChart"; import TingkatPrestasiChart from "@/components/charts/TingkatPrestasiChart";
import TingkatPrestasiPieChart from "@/components/TingkatPrestasiPieChart"; import TingkatPrestasiPieChart from "@/components/charts/TingkatPrestasiPieChart";
import JenisPendaftaranPrestasiChart from "@/components/JenisPendaftaranPrestasiChart"; import JenisPendaftaranPrestasiChart from "@/components/charts/JenisPendaftaranPrestasiChart";
import JenisPendaftaranPrestasiPieChart from "@/components/JenisPendaftaranPrestasiPieChart"; import JenisPendaftaranPrestasiPieChart from "@/components/charts/JenisPendaftaranPrestasiPieChart";
import AsalDaerahPrestasiChart from "@/components/AsalDaerahPrestasiChart"; import AsalDaerahPrestasiChart from "@/components/charts/AsalDaerahPrestasiChart";
import IPKPrestasiChart from "@/components/IPKPrestasiChart"; import IPKPrestasiChart from "@/components/charts/IPKPrestasiChart";
export default function BerprestasiMahasiswaPage() { export default function BerprestasiMahasiswaPage() {
const [selectedYear, setSelectedYear] = useState<string>("all"); const [selectedYear, setSelectedYear] = useState<string>("all");

View File

@@ -3,12 +3,12 @@
import { useState } from "react"; import { useState } from "react";
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan"; import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import LulusTepatWaktuChart from "@/components/LulusTepatWaktuChart"; import LulusTepatWaktuChart from "@/components/charts/LulusTepatWaktuChart";
import LulusTepatWaktuPieChart from "@/components/LulusTepatWaktuPieChart"; import LulusTepatWaktuPieChart from "@/components/charts/LulusTepatWaktuPieChart";
import JenisPendaftaranLulusChart from "@/components/JenisPendaftaranLulusChart"; import JenisPendaftaranLulusChart from "@/components/charts/JenisPendaftaranLulusChart";
import JenisPendaftaranLulusPieChart from "@/components/JenisPendaftaranLulusPieChart"; import JenisPendaftaranLulusPieChart from "@/components/charts/JenisPendaftaranLulusPieChart";
import AsalDaerahLulusChart from "@/components/AsalDaerahLulusChart"; import AsalDaerahLulusChart from "@/components/charts/AsalDaerahLulusChart";
import IPKLulusTepatChart from "@/components/IPKLulusTepatChart"; import IPKLulusTepatChart from "@/components/charts/IPKLulusTepatChart";
export default function LulusTepatWaktuPage() { export default function LulusTepatWaktuPage() {
const [selectedYear, setSelectedYear] = useState<string>("all"); const [selectedYear, setSelectedYear] = useState<string>("all");

View File

@@ -3,12 +3,12 @@
import { useState } from "react"; import { useState } from "react";
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan"; import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
import FilterStatusKuliah from "@/components/FilterStatusKuliah"; import FilterStatusKuliah from "@/components/FilterStatusKuliah";
import StatusMahasiswaFilterChart from "@/components/StatusMahasiswaFilterChart"; import StatusMahasiswaFilterChart from "@/components/charts/StatusMahasiswaFilterChart";
import StatusMahasiswaFilterPieChart from "@/components/StatusMahasiswaFilterPieChart"; import StatusMahasiswaFilterPieChart from "@/components/charts/StatusMahasiswaFilterPieChart";
import JenisPendaftaranStatusChart from "@/components/JenisPendaftaranStatusChart"; import JenisPendaftaranStatusChart from "@/components/charts/JenisPendaftaranStatusChart";
import JenisPendaftaranStatusPieChart from "@/components/JenisPendaftaranStatusPieChart"; import JenisPendaftaranStatusPieChart from "@/components/charts/JenisPendaftaranStatusPieChart";
import AsalDaerahStatusChart from '@/components/AsalDaerahStatusChart'; import AsalDaerahStatusChart from '@/components/charts/AsalDaerahStatusChart';
import IpkStatusChart from '@/components/IpkStatusChart'; import IpkStatusChart from '@/components/charts/IpkStatusChart';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function StatusMahasiswaPage() { export default function StatusMahasiswaPage() {

View File

@@ -1,15 +1,15 @@
'use client'; 'use client';
import { useState } from "react"; import { useState } from "react";
import StatistikMahasiswaChart from "@/components/StatistikMahasiswaChart"; import StatistikMahasiswaChart from "@/components/charts/StatistikMahasiswaChart";
import StatistikPerAngkatanChart from "@/components/StatistikPerAngkatanChart"; import StatistikPerAngkatanChart from "@/components/charts/StatistikPerAngkatanChart";
import JenisPendaftaranChart from "@/components/JenisPendaftaranChart"; import JenisPendaftaranChart from "@/components/charts/JenisPendaftaranChart";
import AsalDaerahChart from "@/components/AsalDaerahChart"; import AsalDaerahChart from "@/components/charts/AsalDaerahChart";
import IPKChart from "@/components/IPKChart"; import IPKChart from "@/components/charts/IPKChart";
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan"; import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
import JenisPendaftaranPerAngkatanChart from "@/components/JenisPendaftaranPerAngkatanChart"; import JenisPendaftaranPerAngkatanChart from "@/components/charts/JenisPendaftaranPerAngkatanChart";
import AsalDaerahPerAngkatanChart from "@/components/AsalDaerahPerAngkatanChart"; import AsalDaerahPerAngkatanChart from "@/components/charts/AsalDaerahPerAngkatanChart";
import IPKJenisKelaminChart from "@/components/IPKJenisKelaminChart"; import IPKJenisKelaminChart from "@/components/charts/IPKJenisKelaminChart";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function TotalMahasiswaPage() { export default function TotalMahasiswaPage() {

View File

@@ -4,11 +4,11 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Users, GraduationCap, Trophy, BookOpen } from "lucide-react"; import { Users, GraduationCap, Trophy, BookOpen } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import StatusMahasiswaChart from "@/components/StatusMahasiswaChart"; import StatusMahasiswaChart from "@/components/charts/StatusMahasiswaChart";
import StatistikMahasiswaChart from "@/components/StatistikMahasiswaChart"; import StatistikMahasiswaChart from "@/components/charts/StatistikMahasiswaChart";
import JenisPendaftaranChart from "@/components/JenisPendaftaranChart"; import JenisPendaftaranChart from "@/components/charts/JenisPendaftaranChart";
import AsalDaerahChart from "@/components/AsalDaerahChart"; import AsalDaerahChart from "@/components/charts/AsalDaerahChart";
import IPKChart from '@/components/IPKChart'; import IPKChart from '@/components/charts/IPKChart';
interface MahasiswaTotal { interface MahasiswaTotal {
total_mahasiswa: number; total_mahasiswa: number;

View File

@@ -1,22 +1,22 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Loader2, Eye, EyeOff, LogIn, UserPlus } from "lucide-react"; import { Loader2, Eye, EyeOff } from "lucide-react";
import { ThemeProvider } from '@/components/theme-provider'; import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/ui/toaster'; import { Toaster } from '@/components/ui/toaster';
export default function LandingPage() { export default function LandingPage() {
const router = useRouter(); const router = useRouter();
const [isLoginOpen, setIsLoginOpen] = useState(false); const [isLoginOpen, setIsLoginOpen] = useState(true);
const [isRegisterOpen, setIsRegisterOpen] = useState(false); const [isRegisterOpen, setIsRegisterOpen] = useState(false);
const [activeTab, setActiveTab] = useState('admin'); const [activeTab, setActiveTab] = useState('dosen');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
@@ -146,6 +146,16 @@ export default function LandingPage() {
} }
}; };
const openRegister = () => {
setIsLoginOpen(false);
setIsRegisterOpen(true);
};
const openLogin = () => {
setIsRegisterOpen(false);
setIsLoginOpen(true);
};
return ( return (
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
@@ -154,220 +164,226 @@ export default function LandingPage() {
disableTransitionOnChange disableTransitionOnChange
> >
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
<div className="container mx-auto px-4 py-16"> {/* Login Dialog */}
<div className="text-center mb-12"> <Dialog open={isLoginOpen} onOpenChange={() => {}}>
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 dark:text-white mb-4"> <DialogContent className="sm:max-w-md" hideClose>
Portal Data Informatika <DialogHeader>
</h1> <DialogTitle>Login Portal Data Informatika</DialogTitle>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8"> <DialogDescription>
Visualisasi Data Akademik Mahasiswa Jurusan Informatika Silakan login sesuai dengan role Anda
</p> </DialogDescription>
<div className="flex flex-col sm:flex-row gap-4 justify-center"> </DialogHeader>
<Dialog open={isLoginOpen} onOpenChange={setIsLoginOpen}> <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<DialogTrigger asChild> <TabsList className="grid w-full grid-cols-2">
<Button size="lg" className="flex items-center gap-2"> <TabsTrigger value="dosen">Dosen</TabsTrigger>
<LogIn className="h-5 w-5" /> <TabsTrigger value="admin">Admin</TabsTrigger>
Login </TabsList>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Login Portal Data Informatika</DialogTitle>
<DialogDescription>
Silakan login sesuai dengan role Anda
</DialogDescription>
</DialogHeader>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="admin">Admin</TabsTrigger>
<TabsTrigger value="dosen">Dosen</TabsTrigger>
</TabsList>
<TabsContent value="admin" className="space-y-4"> <TabsContent value="dosen" className="space-y-4">
<form onSubmit={handleAdminLogin} className="space-y-4"> <form onSubmit={handleDosenLogin} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="username">Username</Label> <Label htmlFor="nip">NIP</Label>
<Input <Input
id="username" id="nip"
type="text" type="text"
placeholder="Masukkan username" placeholder="Masukkan NIP"
value={adminForm.username} value={dosenForm.nip}
onChange={(e) => setAdminForm({ ...adminForm, username: e.target.value })} onChange={(e) => setDosenForm({ ...dosenForm, nip: e.target.value })}
required required
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="admin-password">Password</Label> <Label htmlFor="dosen-password">Password</Label>
<div className="relative"> <div className="relative">
<Input
id="admin-password"
type={showPassword ? "text" : "password"}
placeholder="Masukkan password"
value={adminForm.password}
onChange={(e) => setAdminForm({ ...adminForm, password: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Login sebagai Admin
</Button>
</form>
</TabsContent>
<TabsContent value="dosen" className="space-y-4">
<form onSubmit={handleDosenLogin} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="nip">NIP</Label>
<Input
id="nip"
type="text"
placeholder="Masukkan NIP"
value={dosenForm.nip}
onChange={(e) => setDosenForm({ ...dosenForm, nip: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="dosen-password">Password</Label>
<div className="relative">
<Input
id="dosen-password"
type={showPassword ? "text" : "password"}
placeholder="Masukkan password"
value={dosenForm.password}
onChange={(e) => setDosenForm({ ...dosenForm, password: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Login sebagai Dosen
</Button>
</form>
</TabsContent>
</Tabs>
{error && (
<Alert className="mt-4">
<AlertDescription className="text-red-600">
{error}
</AlertDescription>
</Alert>
)}
</DialogContent>
</Dialog>
<Dialog open={isRegisterOpen} onOpenChange={setIsRegisterOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="lg" className="flex items-center gap-2">
<UserPlus className="h-5 w-5" />
Register
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Registrasi Dosen</DialogTitle>
<DialogDescription>
Daftar akun baru untuk dosen Portal Data Informatika
</DialogDescription>
</DialogHeader>
<form onSubmit={handleRegister} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="register-nip">NIP</Label>
<Input <Input
id="register-nip" id="dosen-password"
type="text" type={showPassword ? "text" : "password"}
placeholder="Masukkan NIP" placeholder="Masukkan password"
value={registerForm.nip} value={dosenForm.password}
onChange={(e) => setRegisterForm({ ...registerForm, nip: e.target.value })} onChange={(e) => setDosenForm({ ...dosenForm, password: e.target.value })}
required required
/> />
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div> </div>
<div className="space-y-2"> </div>
<Label htmlFor="register-password">Password</Label> <Button type="submit" className="w-full" disabled={loading}>
<div className="relative"> {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
<Input Login sebagai Dosen
id="register-password" </Button>
type={showPassword ? "text" : "password"} </form>
placeholder="Masukkan password (min. 6 karakter)"
value={registerForm.password} <div className="text-center pt-4 border-t">
onChange={(e) => setRegisterForm({ ...registerForm, password: e.target.value })} <p className="text-sm text-gray-600 dark:text-gray-400 inline">
required Belum punya akun?{' '}
/> </p>
<Button <Button
type="button" type="button"
variant="ghost" variant="link"
size="sm" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 p-0 h-auto inline"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent" onClick={openRegister}
onClick={() => setShowPassword(!showPassword)} >
> Daftar disini
{showPassword ? ( </Button>
<EyeOff className="h-4 w-4" /> </div>
) : ( </TabsContent>
<Eye className="h-4 w-4" />
)} <TabsContent value="admin" className="space-y-4">
</Button> <form onSubmit={handleAdminLogin} className="space-y-4">
</div> <div className="space-y-2">
</div> <Label htmlFor="username">Username</Label>
<div className="space-y-2"> <Input
<Label htmlFor="confirm-password">Konfirmasi Password</Label> id="username"
type="text"
placeholder="Masukkan username"
value={adminForm.username}
onChange={(e) => setAdminForm({ ...adminForm, username: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="admin-password">Password</Label>
<div className="relative">
<Input <Input
id="confirm-password" id="admin-password"
type="password" type={showPassword ? "text" : "password"}
placeholder="Konfirmasi password" placeholder="Masukkan password"
value={registerForm.confirmPassword} value={adminForm.password}
onChange={(e) => setRegisterForm({ ...registerForm, confirmPassword: e.target.value })} onChange={(e) => setAdminForm({ ...adminForm, password: e.target.value })}
required required
/> />
</div> <Button
<Button type="submit" className="w-full" disabled={loading}> type="button"
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} variant="ghost"
Daftar sebagai Dosen size="sm"
</Button> className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
</form> onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Login sebagai Admin
</Button>
</form>
</TabsContent>
</Tabs>
{error && ( {error && (
<Alert className="mt-4"> <Alert className="mt-4">
<AlertDescription className="text-red-600"> <AlertDescription className="text-red-600">
{error} {error}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> {/* Register Dialog */}
</div> <Dialog open={isRegisterOpen} onOpenChange={() => {}}>
<DialogContent className="sm:max-w-md" hideClose>
<DialogHeader>
<DialogTitle>Registrasi Dosen</DialogTitle>
<DialogDescription>
Daftar akun baru untuk dosen Portal Data Informatika
</DialogDescription>
</DialogHeader>
<form onSubmit={handleRegister} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="register-nip">NIP</Label>
<Input
id="register-nip"
type="text"
placeholder="Masukkan NIP"
value={registerForm.nip}
onChange={(e) => setRegisterForm({ ...registerForm, nip: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="register-password">Password</Label>
<div className="relative">
<Input
id="register-password"
type={showPassword ? "text" : "password"}
placeholder="Masukkan password (min. 6 karakter)"
value={registerForm.password}
onChange={(e) => setRegisterForm({ ...registerForm, password: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div> </div>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">Konfirmasi Password</Label>
<Input
id="confirm-password"
type="password"
placeholder="Konfirmasi password"
value={registerForm.confirmPassword}
onChange={(e) => setRegisterForm({ ...registerForm, confirmPassword: e.target.value })}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Daftar sebagai Dosen
</Button>
</form>
<div className="text-center pt-4 border-t">
<p className="text-sm text-gray-600 dark:text-gray-400 inline">
Sudah punya akun?{' '}
</p>
<Button
type="button"
variant="link"
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 p-0 h-auto inline"
onClick={openLogin}
>
Login
</Button>
</div>
{error && (
<Alert className="mt-4">
<AlertDescription className="text-red-600">
{error}
</AlertDescription>
</Alert>
)}
</DialogContent>
</Dialog>
</div>
<Toaster /> <Toaster />
</ThemeProvider> </ThemeProvider>
); );

View File

@@ -49,8 +49,9 @@ function DialogOverlay({
function DialogContent({ function DialogContent({
className, className,
children, children,
hideClose = false,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) { }: React.ComponentProps<typeof DialogPrimitive.Content> & { hideClose?: boolean }) {
return ( return (
<DialogPortal data-slot="dialog-portal"> <DialogPortal data-slot="dialog-portal">
<DialogOverlay /> <DialogOverlay />
@@ -63,10 +64,12 @@ function DialogContent({
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"> {!hideClose && (
<XIcon /> <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<span className="sr-only">Close</span> <XIcon />
</DialogPrimitive.Close> <span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
) )