189 lines
9.8 KiB
TypeScript
189 lines
9.8 KiB
TypeScript
|
|
import LandlordLayout from "@/Layouts/LandlordLayout";
|
|||
|
|
import { Link, router } from "@inertiajs/react";
|
|||
|
|
import { Plus, Edit, Trash2, Globe } from "lucide-react";
|
|||
|
|
import { useState } from "react";
|
|||
|
|
import {
|
|||
|
|
AlertDialog,
|
|||
|
|
AlertDialogAction,
|
|||
|
|
AlertDialogCancel,
|
|||
|
|
AlertDialogContent,
|
|||
|
|
AlertDialogDescription,
|
|||
|
|
AlertDialogFooter,
|
|||
|
|
AlertDialogHeader,
|
|||
|
|
AlertDialogTitle,
|
|||
|
|
} from "@/Components/ui/alert-dialog";
|
|||
|
|
|
|||
|
|
interface Tenant {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
email: string | null;
|
|||
|
|
is_active: boolean;
|
|||
|
|
created_at: string;
|
|||
|
|
domains: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
tenants: Tenant[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function TenantIndex({ tenants }: Props) {
|
|||
|
|
const [deleteTarget, setDeleteTarget] = useState<Tenant | null>(null);
|
|||
|
|
|
|||
|
|
const handleDelete = () => {
|
|||
|
|
if (deleteTarget) {
|
|||
|
|
router.delete(route("landlord.tenants.destroy", deleteTarget.id));
|
|||
|
|
setDeleteTarget(null);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<LandlordLayout title="租戶管理">
|
|||
|
|
<div className="space-y-6">
|
|||
|
|
{/* Header */}
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div>
|
|||
|
|
<h1 className="text-2xl font-bold text-slate-900">租戶管理</h1>
|
|||
|
|
<p className="text-slate-500 mt-1">管理系統中的所有租戶</p>
|
|||
|
|
</div>
|
|||
|
|
<Link
|
|||
|
|
href="/landlord/tenants/create"
|
|||
|
|
className="bg-primary-main hover:bg-primary-dark text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
|
|||
|
|
>
|
|||
|
|
<Plus className="w-4 h-4" />
|
|||
|
|
新增租戶
|
|||
|
|
</Link>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Table */}
|
|||
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|||
|
|
<table className="w-full">
|
|||
|
|
<thead className="bg-slate-50">
|
|||
|
|
<tr>
|
|||
|
|
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
ID
|
|||
|
|
</th>
|
|||
|
|
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
名稱
|
|||
|
|
</th>
|
|||
|
|
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
域名
|
|||
|
|
</th>
|
|||
|
|
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
狀態
|
|||
|
|
</th>
|
|||
|
|
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
建立時間
|
|||
|
|
</th>
|
|||
|
|
<th className="px-6 py-3 text-right text-xs font-semibold text-slate-500 uppercase">
|
|||
|
|
操作
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="divide-y divide-slate-100">
|
|||
|
|
{tenants.length === 0 ? (
|
|||
|
|
<tr>
|
|||
|
|
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">
|
|||
|
|
尚無租戶資料,請點擊「新增租戶」建立第一個租戶
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
) : (
|
|||
|
|
tenants.map((tenant) => (
|
|||
|
|
<tr key={tenant.id} className="hover:bg-slate-50">
|
|||
|
|
<td className="px-6 py-4 font-mono text-sm text-slate-600">
|
|||
|
|
{tenant.id}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-6 py-4">
|
|||
|
|
<div>
|
|||
|
|
<p className="font-medium text-slate-900">{tenant.name}</p>
|
|||
|
|
{tenant.email && (
|
|||
|
|
<p className="text-sm text-slate-500">{tenant.email}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-6 py-4">
|
|||
|
|
{tenant.domains.length > 0 ? (
|
|||
|
|
<div className="flex items-center gap-1 flex-wrap">
|
|||
|
|
{tenant.domains.map((domain) => (
|
|||
|
|
<span
|
|||
|
|
key={domain}
|
|||
|
|
className="inline-flex items-center gap-1 px-2 py-1 bg-slate-100 rounded text-xs"
|
|||
|
|
>
|
|||
|
|
<Globe className="w-3 h-3" />
|
|||
|
|
{domain}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<span className="text-slate-400 text-sm">無</span>
|
|||
|
|
)}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-6 py-4">
|
|||
|
|
<span
|
|||
|
|
className={`px-2 py-1 rounded-full text-xs font-medium ${tenant.is_active
|
|||
|
|
? "bg-green-100 text-green-700"
|
|||
|
|
: "bg-slate-100 text-slate-600"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{tenant.is_active ? "啟用" : "停用"}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-6 py-4 text-sm text-slate-500">
|
|||
|
|
{tenant.created_at}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-6 py-4 text-right">
|
|||
|
|
<div className="flex items-center justify-end gap-2">
|
|||
|
|
<Link
|
|||
|
|
href={`/landlord/tenants/${tenant.id}`}
|
|||
|
|
className="p-2 text-slate-400 hover:text-primary-main hover:bg-slate-100 rounded-lg transition-colors"
|
|||
|
|
title="查看詳情"
|
|||
|
|
>
|
|||
|
|
<Globe className="w-4 h-4" />
|
|||
|
|
</Link>
|
|||
|
|
<Link
|
|||
|
|
href={`/landlord/tenants/${tenant.id}/edit`}
|
|||
|
|
className="p-2 text-slate-400 hover:text-blue-600 hover:bg-slate-100 rounded-lg transition-colors"
|
|||
|
|
title="編輯"
|
|||
|
|
>
|
|||
|
|
<Edit className="w-4 h-4" />
|
|||
|
|
</Link>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setDeleteTarget(tenant)}
|
|||
|
|
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
|||
|
|
title="刪除"
|
|||
|
|
>
|
|||
|
|
<Trash2 className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
))
|
|||
|
|
)}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Delete Confirmation */}
|
|||
|
|
<AlertDialog open={!!deleteTarget} onOpenChange={() => setDeleteTarget(null)}>
|
|||
|
|
<AlertDialogContent>
|
|||
|
|
<AlertDialogHeader>
|
|||
|
|
<AlertDialogTitle>確定要刪除這個租戶嗎?</AlertDialogTitle>
|
|||
|
|
<AlertDialogDescription>
|
|||
|
|
刪除租戶將會同時刪除其資料庫和所有資料,此操作無法復原。
|
|||
|
|
</AlertDialogDescription>
|
|||
|
|
</AlertDialogHeader>
|
|||
|
|
<AlertDialogFooter>
|
|||
|
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
|||
|
|
<AlertDialogAction
|
|||
|
|
onClick={handleDelete}
|
|||
|
|
className="bg-red-600 hover:bg-red-700"
|
|||
|
|
>
|
|||
|
|
確定刪除
|
|||
|
|
</AlertDialogAction>
|
|||
|
|
</AlertDialogFooter>
|
|||
|
|
</AlertDialogContent>
|
|||
|
|
</AlertDialog>
|
|||
|
|
</LandlordLayout>
|
|||
|
|
);
|
|||
|
|
}
|