Coba terus
This commit is contained in:
214
app/api/keloladata/kelompok-keahlian/route.ts
Normal file
214
app/api/keloladata/kelompok-keahlian/route.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import supabase from '@/lib/db';
|
||||||
|
|
||||||
|
// GET - Get all kelompok keahlian
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.select('id_kk, nama_kelompok')
|
||||||
|
.order('id_kk', { ascending: true });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error fetching kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to fetch kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to fetch kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST - Create new kelompok keahlian
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { nama_kelompok } = await request.json();
|
||||||
|
|
||||||
|
if (!nama_kelompok || nama_kelompok.trim() === '') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Nama kelompok keahlian is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nama_kelompok already exists
|
||||||
|
const { data: existingData, error: existingError } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.select('id_kk')
|
||||||
|
.ilike('nama_kelompok', nama_kelompok.trim());
|
||||||
|
|
||||||
|
if (existingError) {
|
||||||
|
console.error('Error checking existing kelompok keahlian:', existingError);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to check existing kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingData && existingData.length > 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Kelompok keahlian dengan nama tersebut sudah ada' },
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.insert([{ nama_kelompok: nama_kelompok.trim() }])
|
||||||
|
.select('id_kk, nama_kelompok')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error creating kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to create kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(data, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to create kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT - Update kelompok keahlian
|
||||||
|
export async function PUT(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { id_kk, nama_kelompok } = await request.json();
|
||||||
|
|
||||||
|
if (!id_kk || !nama_kelompok || nama_kelompok.trim() === '') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'ID dan nama kelompok keahlian are required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if kelompok keahlian exists
|
||||||
|
const { data: existingData, error: existingError } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.select('id_kk')
|
||||||
|
.eq('id_kk', id_kk)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (existingError || !existingData) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Kelompok keahlian tidak ditemukan' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nama_kelompok already exists for other records
|
||||||
|
const { data: duplicateData, error: duplicateError } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.select('id_kk')
|
||||||
|
.ilike('nama_kelompok', nama_kelompok.trim())
|
||||||
|
.neq('id_kk', id_kk);
|
||||||
|
|
||||||
|
if (duplicateError) {
|
||||||
|
console.error('Error checking duplicate kelompok keahlian:', duplicateError);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to check duplicate kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateData && duplicateData.length > 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Kelompok keahlian dengan nama tersebut sudah ada' },
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.update({ nama_kelompok: nama_kelompok.trim() })
|
||||||
|
.eq('id_kk', id_kk)
|
||||||
|
.select('id_kk, nama_kelompok')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error updating kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to update kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to update kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE - Delete kelompok keahlian
|
||||||
|
export async function DELETE(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const id_kk = searchParams.get('id_kk');
|
||||||
|
|
||||||
|
if (!id_kk) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'ID kelompok keahlian is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if kelompok keahlian exists
|
||||||
|
const { data: existingData, error: existingError } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.select('id_kk')
|
||||||
|
.eq('id_kk', id_kk)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (existingError || !existingData) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Kelompok keahlian tidak ditemukan' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if kelompok keahlian is being used in other tables
|
||||||
|
// You might want to add foreign key checks here depending on your database structure
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('kelompok_keahlian')
|
||||||
|
.delete()
|
||||||
|
.eq('id_kk', id_kk);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error deleting kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to delete kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: 'Kelompok keahlian berhasil dihapus' },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting kelompok keahlian:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to delete kelompok keahlian' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,11 +43,13 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
X,
|
X,
|
||||||
Loader2,
|
Loader2,
|
||||||
RefreshCw
|
RefreshCw,
|
||||||
|
Users
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import EditJenisPendaftaran from "@/components/datatable/edit-jenis-pendaftaran";
|
import EditJenisPendaftaran from "@/components/datatable/edit-jenis-pendaftaran";
|
||||||
import UploadExcelMahasiswa from "@/components/datatable/upload-excel-mahasiswa";
|
import UploadExcelMahasiswa from "@/components/datatable/upload-excel-mahasiswa";
|
||||||
import { useToast } from "@/components/ui/toast-provider";
|
import { useToast } from "@/components/ui/toast-provider";
|
||||||
|
|
||||||
// Define the Mahasiswa type based on API route structure
|
// Define the Mahasiswa type based on API route structure
|
||||||
interface Mahasiswa {
|
interface Mahasiswa {
|
||||||
nim: string;
|
nim: string;
|
||||||
@@ -67,6 +69,12 @@ interface Mahasiswa {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the KelompokKeahlian type
|
||||||
|
interface KelompokKeahlian {
|
||||||
|
id_kk: number;
|
||||||
|
nama_kelompok: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function DataTableMahasiswa() {
|
export default function DataTableMahasiswa() {
|
||||||
const { showSuccess, showError } = useToast();
|
const { showSuccess, showError } = useToast();
|
||||||
// State for data
|
// State for data
|
||||||
@@ -107,11 +115,22 @@ export default function DataTableMahasiswa() {
|
|||||||
// State for kelompok keahlian options
|
// State for kelompok keahlian options
|
||||||
const [kelompokKeahlianOptions, setKelompokKeahlianOptions] = useState<Array<{id_kk: number, nama_kelompok: string}>>([]);
|
const [kelompokKeahlianOptions, setKelompokKeahlianOptions] = useState<Array<{id_kk: number, nama_kelompok: string}>>([]);
|
||||||
|
|
||||||
|
// State for kelompok keahlian CRUD
|
||||||
|
const [kelompokKeahlianData, setKelompokKeahlianData] = useState<KelompokKeahlian[]>([]);
|
||||||
|
const [kelompokKeahlianFormMode, setKelompokKeahlianFormMode] = useState<"add" | "edit">("add");
|
||||||
|
const [kelompokKeahlianFormData, setKelompokKeahlianFormData] = useState<Partial<KelompokKeahlian>>({});
|
||||||
|
const [isKelompokKeahlianSubmitting, setIsKelompokKeahlianSubmitting] = useState(false);
|
||||||
|
const [isKelompokKeahlianDialogOpen, setIsKelompokKeahlianDialogOpen] = useState(false);
|
||||||
|
const [deleteKelompokKeahlianId, setDeleteKelompokKeahlianId] = useState<number | null>(null);
|
||||||
|
const [isKelompokKeahlianDeleting, setIsKelompokKeahlianDeleting] = useState(false);
|
||||||
|
const [isKelompokKeahlianDeleteDialogOpen, setIsKelompokKeahlianDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
// Fetch data on component mount
|
// Fetch data on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMahasiswa();
|
fetchMahasiswa();
|
||||||
fetchJenisPendaftaranOptions();
|
fetchJenisPendaftaranOptions();
|
||||||
fetchKelompokKeahlianOptions();
|
fetchKelompokKeahlianOptions();
|
||||||
|
fetchKelompokKeahlianData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Filter data when search term or filter changes
|
// Filter data when search term or filter changes
|
||||||
@@ -146,6 +165,22 @@ export default function DataTableMahasiswa() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch kelompok keahlian data
|
||||||
|
const fetchKelompokKeahlianData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/keloladata/kelompok-keahlian");
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch kelompok keahlian data");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setKelompokKeahlianData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching kelompok keahlian data:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Update semester for active students
|
// Update semester for active students
|
||||||
const handleUpdateSemester = async () => {
|
const handleUpdateSemester = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -400,6 +435,104 @@ export default function DataTableMahasiswa() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Kelompok Keahlian CRUD functions
|
||||||
|
const handleKelompokKeahlianAdd = () => {
|
||||||
|
setKelompokKeahlianFormMode("add");
|
||||||
|
setKelompokKeahlianFormData({});
|
||||||
|
setIsKelompokKeahlianDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKelompokKeahlianEdit = (data: KelompokKeahlian) => {
|
||||||
|
setKelompokKeahlianFormMode("edit");
|
||||||
|
setKelompokKeahlianFormData(data);
|
||||||
|
setIsKelompokKeahlianDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKelompokKeahlianDeleteConfirm = (id: number) => {
|
||||||
|
setDeleteKelompokKeahlianId(id);
|
||||||
|
setIsKelompokKeahlianDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKelompokKeahlianSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsKelompokKeahlianSubmitting(true);
|
||||||
|
|
||||||
|
if (kelompokKeahlianFormMode === "add") {
|
||||||
|
// Add new kelompok keahlian
|
||||||
|
const response = await fetch("/api/keloladata/kelompok-keahlian", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(kelompokKeahlianFormData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || "Failed to add kelompok keahlian");
|
||||||
|
}
|
||||||
|
showSuccess("Kelompok keahlian berhasil ditambahkan!");
|
||||||
|
} else {
|
||||||
|
// Edit existing kelompok keahlian
|
||||||
|
const response = await fetch("/api/keloladata/kelompok-keahlian", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(kelompokKeahlianFormData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || "Failed to update kelompok keahlian");
|
||||||
|
}
|
||||||
|
showSuccess("Kelompok keahlian berhasil diperbarui!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data after successful operation
|
||||||
|
await fetchKelompokKeahlianData();
|
||||||
|
await fetchKelompokKeahlianOptions();
|
||||||
|
setIsKelompokKeahlianDialogOpen(false);
|
||||||
|
setKelompokKeahlianFormData({});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error submitting kelompok keahlian form:", err);
|
||||||
|
showError(`Gagal ${kelompokKeahlianFormMode === "add" ? "menambahkan" : "memperbarui"} kelompok keahlian.`);
|
||||||
|
} finally {
|
||||||
|
setIsKelompokKeahlianSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKelompokKeahlianDelete = async () => {
|
||||||
|
if (!deleteKelompokKeahlianId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsKelompokKeahlianDeleting(true);
|
||||||
|
|
||||||
|
const response = await fetch(`/api/keloladata/kelompok-keahlian?id_kk=${deleteKelompokKeahlianId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || "Failed to delete kelompok keahlian");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data after successful deletion
|
||||||
|
await fetchKelompokKeahlianData();
|
||||||
|
await fetchKelompokKeahlianOptions();
|
||||||
|
setIsKelompokKeahlianDeleteDialogOpen(false);
|
||||||
|
setDeleteKelompokKeahlianId(null);
|
||||||
|
showSuccess("Kelompok keahlian berhasil dihapus!");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error deleting kelompok keahlian:", err);
|
||||||
|
showError("Gagal menghapus kelompok keahlian.");
|
||||||
|
} finally {
|
||||||
|
setIsKelompokKeahlianDeleting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Get unique angkatan years for filter
|
// Get unique angkatan years for filter
|
||||||
const getUniqueAngkatan = () => {
|
const getUniqueAngkatan = () => {
|
||||||
const years = new Set<string>();
|
const years = new Set<string>();
|
||||||
@@ -505,6 +638,13 @@ export default function DataTableMahasiswa() {
|
|||||||
)}
|
)}
|
||||||
Update Semester Mahasiswa Aktif
|
Update Semester Mahasiswa Aktif
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleKelompokKeahlianAdd}
|
||||||
|
>
|
||||||
|
<Users className="mr-2 h-4 w-4" />
|
||||||
|
Kelola Kelompok Keahlian
|
||||||
|
</Button>
|
||||||
<EditJenisPendaftaran onUpdateSuccess={fetchMahasiswa} />
|
<EditJenisPendaftaran onUpdateSuccess={fetchMahasiswa} />
|
||||||
<UploadExcelMahasiswa onUploadSuccess={fetchMahasiswa} />
|
<UploadExcelMahasiswa onUploadSuccess={fetchMahasiswa} />
|
||||||
</div>
|
</div>
|
||||||
@@ -701,17 +841,17 @@ export default function DataTableMahasiswa() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Add/Edit Dialog */}
|
{/* Add/Edit Dialog */}
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<DialogContent className="sm:max-w-[900px] max-h-[90vh] overflow-y-auto">
|
<DialogContent className="w-[95vw] max-w-[1200px] max-h-[90vh] overflow-y-auto p-4 sm:p-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{formMode === "add" ? "Tambah Mahasiswa" : "Edit Mahasiswa"}
|
{formMode === "add" ? "Tambah Mahasiswa" : "Edit Mahasiswa"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="grid gap-6 py-4">
|
<div className="grid gap-4 sm:gap-6 py-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label htmlFor="nim" className="text-sm font-medium">
|
<label htmlFor="nim" className="text-sm font-medium">
|
||||||
NIM <span className="text-destructive">*</span>
|
NIM <span className="text-destructive">*</span>
|
||||||
@@ -838,7 +978,7 @@ export default function DataTableMahasiswa() {
|
|||||||
value={formData.id_kelompok_keahlian?.toString() || ""}
|
value={formData.id_kelompok_keahlian?.toString() || ""}
|
||||||
onValueChange={(value) => handleSelectChange("id_kelompok_keahlian", value)}
|
onValueChange={(value) => handleSelectChange("id_kelompok_keahlian", value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Pilih Kelompok Keahlian" />
|
<SelectValue placeholder="Pilih Kelompok Keahlian" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -886,13 +1026,13 @@ export default function DataTableMahasiswa() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter className="flex flex-col sm:flex-row gap-2 sm:gap-0">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="button" variant="outline">
|
<Button type="button" variant="outline" className="w-full sm:w-auto">
|
||||||
Batal
|
Batal
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
<Button type="submit" disabled={isSubmitting} className="w-full sm:w-auto">
|
||||||
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
{formMode === "add" ? "Tambah" : "Simpan"}
|
{formMode === "add" ? "Tambah" : "Simpan"}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -903,7 +1043,7 @@ export default function DataTableMahasiswa() {
|
|||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
{/* Delete Confirmation Dialog */}
|
||||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||||
<DialogContent className="sm:max-w-[400px]">
|
<DialogContent className="w-[95vw] max-w-[400px] p-4 sm:p-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Konfirmasi Hapus</DialogTitle>
|
<DialogTitle>Konfirmasi Hapus</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -913,9 +1053,9 @@ export default function DataTableMahasiswa() {
|
|||||||
Tindakan ini tidak dapat dibatalkan.
|
Tindakan ini tidak dapat dibatalkan.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter className="flex flex-col sm:flex-row gap-2 sm:gap-0">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="button" variant="outline">
|
<Button type="button" variant="outline" className="w-full sm:w-auto">
|
||||||
Batal
|
Batal
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
@@ -923,6 +1063,7 @@ export default function DataTableMahasiswa() {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
{isDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
{isDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Hapus
|
Hapus
|
||||||
@@ -930,6 +1071,128 @@ export default function DataTableMahasiswa() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Kelompok Keahlian CRUD Dialog */}
|
||||||
|
<Dialog open={isKelompokKeahlianDialogOpen} onOpenChange={setIsKelompokKeahlianDialogOpen}>
|
||||||
|
<DialogContent className="w-[95vw] max-w-[600px] max-h-[90vh] overflow-y-auto p-4 sm:p-6">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{kelompokKeahlianFormMode === "add" ? "Tambah Kelompok Keahlian" : "Edit Kelompok Keahlian"}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{/* Kelompok Keahlian Table */}
|
||||||
|
<div className="border rounded-md mb-4 overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="min-w-[60px]">ID</TableHead>
|
||||||
|
<TableHead className="min-w-[200px]">Nama Kelompok</TableHead>
|
||||||
|
<TableHead className="text-right min-w-[100px]">Aksi</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{kelompokKeahlianData.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={3} className="text-center py-4">
|
||||||
|
Tidak ada data kelompok keahlian
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
kelompokKeahlianData.map((kelompok) => (
|
||||||
|
<TableRow key={kelompok.id_kk}>
|
||||||
|
<TableCell className="font-medium">{kelompok.id_kk}</TableCell>
|
||||||
|
<TableCell className="break-words">{kelompok.nama_kelompok}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<div className="flex justify-end gap-1 sm:gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleKelompokKeahlianEdit(kelompok)}
|
||||||
|
className="h-8 w-8 p-0 sm:h-9 sm:w-auto sm:px-3"
|
||||||
|
>
|
||||||
|
<Pencil className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-destructive hover:bg-destructive/10 h-8 w-8 p-0 sm:h-9 sm:w-auto sm:px-3"
|
||||||
|
onClick={() => handleKelompokKeahlianDeleteConfirm(kelompok.id_kk)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Kelompok Keahlian Form */}
|
||||||
|
<form onSubmit={handleKelompokKeahlianSubmit}>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="nama_kelompok" className="text-sm font-medium">
|
||||||
|
Nama Kelompok Keahlian <span className="text-destructive">*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="nama_kelompok"
|
||||||
|
name="nama_kelompok"
|
||||||
|
value={kelompokKeahlianFormData.nama_kelompok || ""}
|
||||||
|
onChange={(e) => setKelompokKeahlianFormData(prev => ({ ...prev, nama_kelompok: e.target.value }))}
|
||||||
|
required
|
||||||
|
placeholder="Masukkan nama kelompok keahlian"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter className="flex flex-col sm:flex-row gap-2 sm:gap-0">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" variant="outline" className="w-full sm:w-auto">
|
||||||
|
Tutup
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button type="submit" disabled={isKelompokKeahlianSubmitting} className="w-full sm:w-auto">
|
||||||
|
{isKelompokKeahlianSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
{kelompokKeahlianFormMode === "add" ? "Tambah" : "Simpan"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Kelompok Keahlian Delete Confirmation Dialog */}
|
||||||
|
<Dialog open={isKelompokKeahlianDeleteDialogOpen} onOpenChange={setIsKelompokKeahlianDeleteDialogOpen}>
|
||||||
|
<DialogContent className="w-[95vw] max-w-[400px] p-4 sm:p-6">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Konfirmasi Hapus Kelompok Keahlian</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="py-4">
|
||||||
|
<p>Apakah Anda yakin ingin menghapus kelompok keahlian ini?</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Tindakan ini tidak dapat dibatalkan.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<DialogFooter className="flex flex-col sm:flex-row gap-2 sm:gap-0">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" variant="outline" className="w-full sm:w-auto">
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleKelompokKeahlianDelete}
|
||||||
|
disabled={isKelompokKeahlianDeleting}
|
||||||
|
className="w-full sm:w-auto"
|
||||||
|
>
|
||||||
|
{isKelompokKeahlianDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
Hapus
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user