[FEAT] 實作公共事業費逾期提醒、租戶自訂通知設定及發送測試信功能
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 56s

This commit is contained in:
2026-03-05 16:01:00 +08:00
parent 016366407c
commit 07b7d9b327
15 changed files with 519 additions and 44 deletions

View File

@@ -1,6 +1,6 @@
import React from "react";
import React, { useState } from "react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, useForm } from "@inertiajs/react";
import { Head, useForm, router } from "@inertiajs/react";
import {
Card,
CardContent,
@@ -17,8 +17,10 @@ import {
Package,
RefreshCcw,
Monitor,
Bell,
Save,
Settings
Settings,
Send
} from "lucide-react";
import { toast } from "sonner";
@@ -33,6 +35,7 @@ interface PageProps {
}
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,
@@ -40,6 +43,8 @@ export default function SettingIndex({ settings }: PageProps) {
}))
});
const [activeTab, setActiveTab] = useState("finance");
const handleValueChange = (key: string, value: string) => {
const newSettings = data.settings.map(s =>
s.key === key ? { ...s, value } : s
@@ -54,6 +59,14 @@ export default function SettingIndex({ settings }: PageProps) {
});
};
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 || '';
@@ -65,11 +78,14 @@ export default function SettingIndex({ settings }: PageProps) {
</div>
<div>
<Input
type="text"
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>
);
@@ -96,7 +112,7 @@ export default function SettingIndex({ settings }: PageProps) {
</div>
<form onSubmit={submit}>
<Tabs defaultValue="finance" className="space-y-6">
<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" />
@@ -110,6 +126,9 @@ export default function SettingIndex({ settings }: PageProps) {
<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">
@@ -160,7 +179,31 @@ export default function SettingIndex({ settings }: PageProps) {
</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"
@@ -176,3 +219,4 @@ export default function SettingIndex({ settings }: PageProps) {
</AuthenticatedLayout>
);
}