feat: add leadership history dialog and integrate into DepartmentIndex
This commit is contained in:
parent
f00fab9520
commit
2b458e26fa
@ -14,7 +14,11 @@ class DepartmentService
|
|||||||
public function paginated(int $perPage = 25, string $search = ''): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = ''): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Department::query()
|
return Department::query()
|
||||||
->with(['currentLeader.lecturer.user.profile'])
|
->with([
|
||||||
|
'currentLeader.lecturer.user.profile',
|
||||||
|
'leaderships' => fn ($q) => $q->orderByRaw('ended_at IS NULL DESC')->orderByDesc('started_at'),
|
||||||
|
'leaderships.lecturer.user.profile',
|
||||||
|
])
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))
|
||||||
->latest()
|
->latest()
|
||||||
->paginate($perPage, ['id', 'code', 'name', 'degree_level']);
|
->paginate($perPage, ['id', 'code', 'name', 'degree_level']);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ColumnDef } from '@tanstack/react-table';
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
import { Pencil, Trash2 } from 'lucide-react';
|
import { History, Pencil, Trash2 } from 'lucide-react';
|
||||||
import { RowActions } from '@/components/row-actions';
|
import { RowActions } from '@/components/row-actions';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import type { Department } from '@/types/department';
|
import type { Department } from '@/types/department';
|
||||||
@ -9,6 +9,7 @@ export type { Department } from '@/types/department';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (department: Department) => void;
|
handleEdit: (department: Department) => void;
|
||||||
handleDeleteClick: (department: Department) => void;
|
handleDeleteClick: (department: Department) => void;
|
||||||
|
handleViewHistory: (department: Department) => void;
|
||||||
canUpdate: boolean;
|
canUpdate: boolean;
|
||||||
canDelete: boolean;
|
canDelete: boolean;
|
||||||
};
|
};
|
||||||
@ -16,7 +17,13 @@ type CreateColumnsParams = {
|
|||||||
export function createDepartmentColumns(
|
export function createDepartmentColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Department>[] {
|
): ColumnDef<Department>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const {
|
||||||
|
handleEdit,
|
||||||
|
handleDeleteClick,
|
||||||
|
handleViewHistory,
|
||||||
|
canUpdate,
|
||||||
|
canDelete,
|
||||||
|
} = params;
|
||||||
|
|
||||||
const columns: ColumnDef<Department>[] = [
|
const columns: ColumnDef<Department>[] = [
|
||||||
{
|
{
|
||||||
@ -72,7 +79,7 @@ export function createDepartmentColumns(
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (canUpdate || canDelete) {
|
{
|
||||||
columns.push({
|
columns.push({
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: () => <span className="block text-center">Aksi</span>,
|
header: () => <span className="block text-center">Aksi</span>,
|
||||||
@ -86,6 +93,11 @@ export function createDepartmentColumns(
|
|||||||
return (
|
return (
|
||||||
<RowActions
|
<RowActions
|
||||||
actions={[
|
actions={[
|
||||||
|
{
|
||||||
|
label: 'Riwayat Kaprodi',
|
||||||
|
icon: <History className="h-4 w-4" />,
|
||||||
|
onClick: () => handleViewHistory(department),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
|||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Combobox,
|
Combobox,
|
||||||
@ -18,6 +19,12 @@ import {
|
|||||||
ComboboxItem,
|
ComboboxItem,
|
||||||
ComboboxList,
|
ComboboxList,
|
||||||
} from '@/components/ui/combobox';
|
} from '@/components/ui/combobox';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
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 {
|
import {
|
||||||
@ -63,6 +70,8 @@ export default function DepartmentIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Department | null>(null);
|
const [editing, setEditing] = useState<Department | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Department | null>(null);
|
const [deleting, setDeleting] = useState<Department | null>(null);
|
||||||
|
const [historyDepartment, setHistoryDepartment] =
|
||||||
|
useState<Department | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
const { hasPermission } = usePermissions();
|
||||||
const canCreate = hasPermission('create-departments');
|
const canCreate = hasPermission('create-departments');
|
||||||
const canUpdate = hasPermission('update-departments');
|
const canUpdate = hasPermission('update-departments');
|
||||||
@ -98,6 +107,7 @@ export default function DepartmentIndex({
|
|||||||
const columns = createDepartmentColumns({
|
const columns = createDepartmentColumns({
|
||||||
handleEdit: (department) => setEditing(department),
|
handleEdit: (department) => setEditing(department),
|
||||||
handleDeleteClick: (department) => setDeleting(department),
|
handleDeleteClick: (department) => setDeleting(department),
|
||||||
|
handleViewHistory: (department) => setHistoryDepartment(department),
|
||||||
canUpdate,
|
canUpdate,
|
||||||
canDelete,
|
canDelete,
|
||||||
});
|
});
|
||||||
@ -188,11 +198,79 @@ export default function DepartmentIndex({
|
|||||||
}
|
}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<LeadershipHistoryDialog
|
||||||
|
department={historyDepartment}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
setHistoryDepartment(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LeadershipHistoryDialog({
|
||||||
|
department,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
department: Department | null;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const entries = department?.leaderships ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={department !== null} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-h-[85vh] overflow-hidden sm:max-w-lg">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
Riwayat Kaprodi
|
||||||
|
{department ? ` — ${department.name}` : ''}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="max-h-[60vh] space-y-3 overflow-y-auto">
|
||||||
|
{entries.length === 0 && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Belum ada riwayat kaprodi untuk jurusan ini.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<div
|
||||||
|
key={entry.id}
|
||||||
|
className="flex items-start justify-between gap-3 rounded-md border p-3"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">
|
||||||
|
{entry.lecturer?.user?.profile
|
||||||
|
?.full_name ?? 'N/A'}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{format(
|
||||||
|
new Date(entry.started_at),
|
||||||
|
'd MMM yyyy',
|
||||||
|
)}{' '}
|
||||||
|
-{' '}
|
||||||
|
{entry.ended_at
|
||||||
|
? format(
|
||||||
|
new Date(entry.ended_at),
|
||||||
|
'd MMM yyyy',
|
||||||
|
)
|
||||||
|
: 'Sekarang'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{!entry.ended_at && <Badge>Sekarang</Badge>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function KaprodiFields({
|
function KaprodiFields({
|
||||||
errors,
|
errors,
|
||||||
lecturers,
|
lecturers,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export type Department = {
|
|||||||
name: string;
|
name: string;
|
||||||
degree_level: DegreeLevel | null;
|
degree_level: DegreeLevel | null;
|
||||||
current_leader: DepartmentLeadership | null;
|
current_leader: DepartmentLeadership | null;
|
||||||
|
leaderships: DepartmentLeadership[];
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user