Files
star-erp/resources/js/Pages/Admin/Setting/Index.tsx
sky121113 07b7d9b327
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 56s
[FEAT] 實作公共事業費逾期提醒、租戶自訂通知設定及發送測試信功能
2026-03-05 16:01:00 +08:00

223 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from "react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, useForm, router } from "@inertiajs/react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from "@/Components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { Button } from "@/Components/ui/button";
import {
Coins,
Package,
RefreshCcw,
Monitor,
Bell,
Save,
Settings,
Send
} from "lucide-react";
import { toast } from "sonner";
interface Setting {
key: string;
value: string;
description: string;
}
interface PageProps {
settings: Record<string, Setting[]>;
}
export default function SettingIndex({ settings }: PageProps) {
const [isTesting, setIsTesting] = useState(false);
const { data, setData, post, processing } = useForm({
settings: Object.values(settings).flat().map(s => ({
key: s.key,
value: s.value
}))
});
const [activeTab, setActiveTab] = useState("finance");
const handleValueChange = (key: string, value: string) => {
const newSettings = data.settings.map(s =>
s.key === key ? { ...s, value } : s
);
setData('settings', newSettings);
};
const submit = (e: React.FormEvent) => {
e.preventDefault();
post(route('settings.update'), {
onSuccess: () => toast.success("系統設定已更新"),
});
};
const handleTestNotification = () => {
setIsTesting(true);
router.post(route('settings.test-notification'), { settings: data.settings }, {
preserveScroll: true,
onFinish: () => setIsTesting(false)
});
};
const renderSettingRow = (setting: Setting) => {
const currentVal = data.settings.find(s => s.key === setting.key)?.value || '';
return (
<div key={setting.key} className="grid grid-cols-1 md:grid-cols-2 gap-4 items-center py-4 border-b last:border-0 border-gray-100">
<div>
<Label className="text-base font-semibold text-grey-0">{setting.description}</Label>
<p className="text-sm text-gray-500 mt-1">{setting.key}</p>
</div>
<div>
<Input
type={setting.key.includes('password') ? 'password' : 'text'}
value={currentVal}
onChange={(e) => handleValueChange(setting.key, e.target.value)}
className="max-w-xs"
/>
{setting.key === 'notification.utility_fee_recipient_emails' && (
<p className="text-xs text-gray-400 mt-1 mt-2">, Emaila@test.com,b@test.com</p>
)}
</div>
</div>
);
};
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: "系統管理", href: "#" },
{ label: "系統設定" }
]}
>
<Head title="系統設定" />
<div className="container mx-auto p-6 max-w-7xl">
<div className="mb-6">
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Settings className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-2">
</p>
</div>
<form onSubmit={submit}>
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="bg-white border p-1 h-auto gap-2">
<TabsTrigger value="finance" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Coins className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="inventory" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Package className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="turnover" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<RefreshCcw className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="display" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Monitor className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="notification" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Bell className="h-4 w-4" />
</TabsTrigger>
</TabsList>
<TabsContent value="finance">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.finance?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="inventory">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.inventory?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="turnover">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>調</CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.turnover?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="display">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.display?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notification">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription> Email </CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.notification?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<div className="flex justify-end gap-4 mt-6">
{activeTab === 'notification' && (
<Button
type="button"
variant="outline"
className="button-outlined-primary"
onClick={handleTestNotification}
disabled={isTesting || processing}
>
<Send className="h-4 w-4 mr-2" />
{isTesting ? "發送中..." : "發送測試信"}
</Button>
)}
<Button
type="submit"
className="button-filled-primary"
disabled={processing}
>
<Save className="h-4 w-4 mr-2" />
{processing ? "儲存中..." : "儲存設定"}
</Button>
</div>
</Tabs>
</form>
</div>
</AuthenticatedLayout>
);
}