Feat: added tab dashboard under General Settings (System Setting and Mail Settings). Improved the Setting API to dynamically use the baseURL.

This commit is contained in:
Tola Leng
2025-05-26 16:51:05 +08:00
parent e74bf35264
commit 7ccd413973
8 changed files with 873 additions and 172 deletions
@@ -1,105 +1,4 @@
import React, { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Settings } from "lucide-react";
import { settingsService, type GeneralSettings } from "@/services/settingsService";
import { useToast } from "@/hooks/use-toast";
import GeneralSettingsPanel from './general/GeneralSettingsPanel';
const GeneralSettingsPanel = () => {
const { toast } = useToast();
const [formData, setFormData] = useState<Partial<GeneralSettings>>({});
const [isEditing, setIsEditing] = useState(false);
const { data: settings, isLoading, error, refetch } = useQuery({
queryKey: ['generalSettings'],
queryFn: settingsService.getGeneralSettings,
});
useEffect(() => {
if (settings) {
setFormData(settings);
}
}, [settings]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSave = async () => {
if (!settings?.id || !formData) return;
try {
const result = await settingsService.updateGeneralSettings(settings.id, formData);
if (result) {
toast({
title: "Settings updated",
description: "Your settings have been updated successfully.",
variant: "default",
});
refetch();
setIsEditing(false);
}
} catch (error) {
console.error("Error updating settings:", error);
toast({
title: "Update failed",
description: "There was a problem updating your settings.",
variant: "destructive",
});
}
};
if (isLoading) {
return <div className="p-4">Loading settings...</div>;
}
if (error) {
return <div className="p-4 text-red-500">Error loading settings</div>;
}
return (
<div className="p-4">
<Card>
<CardHeader>
<CardTitle>System Settings</CardTitle>
<CardDescription>
Configure your system settings and preferences
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label htmlFor="system_name">System Name</Label>
<Input
id="system_name"
name="system_name"
value={formData?.system_name || ''}
onChange={handleChange}
disabled={!isEditing}
placeholder="My Monitoring System"
/>
</div>
</CardContent>
<CardFooter className="flex justify-between">
{isEditing ? (
<>
<Button variant="outline" onClick={() => setIsEditing(false)}>Cancel</Button>
<Button onClick={handleSave}>Save Changes</Button>
</>
) : (
<Button onClick={() => setIsEditing(true)}>Edit Settings</Button>
)}
</CardFooter>
</Card>
</div>
);
};
export default GeneralSettingsPanel;
export default GeneralSettingsPanel;
@@ -0,0 +1,216 @@
import React, { useState, useEffect } from 'react';
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Settings, Mail } from "lucide-react";
import { Form } from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { useLanguage } from "@/contexts/LanguageContext";
import { useSystemSettings } from "@/hooks/useSystemSettings";
import { GeneralSettings } from "@/services/settingsService";
import SystemSettingsTab from './SystemSettingsTab';
import MailSettingsTab from './MailSettingsTab';
import { GeneralSettingsPanelProps } from './types';
const GeneralSettingsPanel: React.FC<GeneralSettingsPanelProps> = () => {
const { t } = useLanguage();
const [isEditing, setIsEditing] = useState(false);
const [activeTab, setActiveTab] = useState("system");
const {
settings,
isLoading,
error,
updateSettings,
isUpdating,
testEmailConnection,
isTestingConnection
} = useSystemSettings();
const form = useForm<GeneralSettings>({
defaultValues: {
meta: {
appName: '',
appURL: '',
senderName: '',
senderAddress: '',
hideControls: false
},
smtp: {
enabled: false,
port: 587,
host: '',
username: '',
authMethod: '',
tls: true,
localName: ''
}
}
});
useEffect(() => {
if (settings) {
// Initialize form with existing settings, using system_name for appName if meta.appName is not set
const appName = settings.meta?.appName || settings.system_name || '';
form.reset({
...settings,
meta: {
appName: appName,
appURL: settings.meta?.appURL || '',
senderName: settings.meta?.senderName || '',
senderAddress: settings.meta?.senderAddress || '',
hideControls: settings.meta?.hideControls || false
},
smtp: settings.smtp || {
enabled: false,
port: 587,
host: '',
username: '',
authMethod: '',
tls: true,
localName: ''
}
});
}
}, [settings, form]);
const handleSave = async (formData: GeneralSettings) => {
try {
// Prepare data for PocketBase settings update (no ID needed)
const dataToSave = {
...formData,
system_name: formData.meta?.appName || settings?.system_name
};
console.log('Saving settings data:', dataToSave);
await updateSettings(dataToSave);
setIsEditing(false);
} catch (error) {
console.error("Error updating settings:", error);
}
};
const handleTestConnection = async () => {
try {
const smtpConfig = form.getValues('smtp');
await testEmailConnection(smtpConfig);
} catch (error) {
console.error("Error testing connection:", error);
}
};
const handleEditClick = () => {
console.log('Edit button clicked, setting isEditing to true');
setIsEditing(true);
};
const handleCancelClick = () => {
console.log('Cancel button clicked, setting isEditing to false');
setIsEditing(false);
// Reset form to original values
if (settings) {
const appName = settings.meta?.appName || settings.system_name || '';
form.reset({
...settings,
meta: {
appName: appName,
appURL: settings.meta?.appURL || '',
senderName: settings.meta?.senderName || '',
senderAddress: settings.meta?.senderAddress || '',
hideControls: settings.meta?.hideControls || false
},
smtp: settings.smtp || {
enabled: false,
port: 587,
host: '',
username: '',
authMethod: '',
tls: true,
localName: ''
}
});
}
};
if (isLoading) {
return <div className="p-4">Loading settings...</div>;
}
if (error) {
return <div className="p-4 text-red-500">Error loading settings</div>;
}
return (
<div className="p-4">
<Card>
<CardHeader>
<CardTitle>{t("generalSettings", "menu")}</CardTitle>
<CardDescription>
{t("monitorSSLCertificates", "ssl")}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSave)}>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="w-full mb-4">
<TabsTrigger value="system" className="flex items-center gap-2 flex-1">
<Settings className="h-4 w-4" />
{t("systemSettings", "settings")}
</TabsTrigger>
<TabsTrigger value="mail" className="flex items-center gap-2 flex-1">
<Mail className="h-4 w-4" />
{t("mailSettings", "settings")}
</TabsTrigger>
</TabsList>
<TabsContent value="system">
<SystemSettingsTab
form={form}
isEditing={isEditing}
settings={settings}
/>
</TabsContent>
<TabsContent value="mail">
<MailSettingsTab
form={form}
isEditing={isEditing}
settings={settings}
handleTestConnection={handleTestConnection}
isTestingConnection={isTestingConnection}
/>
</TabsContent>
</Tabs>
{/* Save and Cancel buttons - only show when editing */}
{isEditing && (
<div className="flex justify-between mt-6">
<Button type="button" variant="outline" onClick={handleCancelClick} disabled={isUpdating}>
{t("cancel", "common")}
</Button>
<Button type="submit" disabled={isUpdating}>
{isUpdating ? t("saving", "settings") : t("save", "settings")}
</Button>
</div>
)}
</form>
</Form>
</CardContent>
{/* Edit button - only show when not editing and outside the form */}
{!isEditing && (
<CardFooter>
<Button type="button" onClick={handleEditClick}>
{t("edit", "common")}
</Button>
</CardFooter>
)}
</Card>
</div>
);
};
export default GeneralSettingsPanel;
@@ -0,0 +1,217 @@
import React from 'react';
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { useLanguage } from "@/contexts/LanguageContext";
import { SettingsTabProps } from "./types";
interface MailSettingsTabProps extends SettingsTabProps {
handleTestConnection: () => Promise<void>;
isTestingConnection: boolean;
}
const MailSettingsTab: React.FC<MailSettingsTabProps> = ({
form,
isEditing,
handleTestConnection,
isTestingConnection
}) => {
const { t } = useLanguage();
return (
<div className="space-y-4">
<div>
<FormField
control={form.control}
name="meta.senderName"
render={({ field }) => (
<FormItem>
<FormLabel>{t("senderName", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="Support"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div>
<FormField
control={form.control}
name="meta.senderAddress"
render={({ field }) => (
<FormItem>
<FormLabel>{t("senderEmail", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="support@example.com"
type="email"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex items-center space-x-2 mb-4">
<FormField
control={form.control}
name="smtp.enabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!isEditing}
/>
</FormControl>
<FormLabel className="mt-0">{t("smtpEnabled", "settings")}</FormLabel>
</FormItem>
)}
/>
</div>
<div className={!form.watch('smtp.enabled') ? 'opacity-50' : ''}>
<div>
<FormField
control={form.control}
name="smtp.host"
render={({ field }) => (
<FormItem>
<FormLabel>{t("smtpHost", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing || !form.watch('smtp.enabled')}
placeholder="smtp.example.com"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="mt-4">
<FormField
control={form.control}
name="smtp.port"
render={({ field }) => (
<FormItem>
<FormLabel>{t("smtpPort", "settings")}</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={e => field.onChange(parseInt(e.target.value) || 587)}
disabled={!isEditing || !form.watch('smtp.enabled')}
placeholder="587"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="mt-4">
<FormField
control={form.control}
name="smtp.username"
render={({ field }) => (
<FormItem>
<FormLabel>{t("smtpUsername", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing || !form.watch('smtp.enabled')}
placeholder="user@example.com"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="mt-4">
<FormField
control={form.control}
name="smtp.authMethod"
render={({ field }) => (
<FormItem>
<FormLabel>{t("smtpAuthMethod", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing || !form.watch('smtp.enabled')}
placeholder="Login"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex items-center space-x-2 mt-4">
<FormField
control={form.control}
name="smtp.tls"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!isEditing || !form.watch('smtp.enabled')}
/>
</FormControl>
<FormLabel className="mt-0">{t("enableTLS", "settings")}</FormLabel>
</FormItem>
)}
/>
</div>
<div className="mt-4">
<FormField
control={form.control}
name="smtp.localName"
render={({ field }) => (
<FormItem>
<FormLabel>{t("localName", "settings")}</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing || !form.watch('smtp.enabled')}
placeholder="localhost"
/>
</FormControl>
</FormItem>
)}
/>
</div>
{isEditing && form.watch('smtp.enabled') && (
<div className="mt-4">
<Button
type="button"
variant="outline"
onClick={handleTestConnection}
disabled={isTestingConnection}
>
{isTestingConnection ? t("testingConnection", "settings") : t("testConnection", "settings")}
</Button>
</div>
)}
</div>
</div>
);
};
export default MailSettingsTab;
@@ -0,0 +1,130 @@
import React from 'react';
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form";
import { useLanguage } from "@/contexts/LanguageContext";
import { SettingsTabProps } from "./types";
const SystemSettingsTab: React.FC<SettingsTabProps> = ({ form, isEditing, settings }) => {
const { t } = useLanguage();
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<FormField
control={form.control}
name="meta.appName"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("appName", "settings")} <span className="text-destructive">*</span>
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="CheckCle"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
value={field.value || settings?.system_name || ''}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div>
<FormField
control={form.control}
name="meta.appURL"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("appURL", "settings")} <span className="text-destructive">*</span>
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="https://pb-api.k8sops.asia"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
<div>
<FormField
control={form.control}
name="meta.senderName"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("senderName", "settings")}
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="System Administrator"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div>
<FormField
control={form.control}
name="meta.senderAddress"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-foreground">
{t("senderEmail", "settings")}
</FormLabel>
<FormControl>
<Input
{...field}
disabled={!isEditing}
placeholder="admin@example.com"
type="email"
className="bg-background border-input text-foreground placeholder:text-muted-foreground disabled:bg-muted disabled:text-muted-foreground"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex items-center space-x-3 pt-4">
<FormField
control={form.control}
name="meta.hideControls"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!isEditing}
/>
</FormControl>
<FormLabel className="text-sm font-medium text-foreground cursor-pointer">
{t("hideControls", "settings")}
</FormLabel>
</FormItem>
)}
/>
</div>
</div>
);
};
export default SystemSettingsTab;
@@ -0,0 +1,12 @@
import { GeneralSettings } from "@/services/settingsService";
export interface SettingsTabProps {
form: any;
isEditing: boolean;
settings?: GeneralSettings;
}
export interface GeneralSettingsPanelProps {
// No props needed for now, but we can add them in the future
}