Files
HRM-System/resources/js/pages/plans/form.tsx
2026-04-13 08:16:56 +08:00

285 lines
10 KiB
TypeScript
Executable File

import React from 'react';
import { PageTemplate } from '@/components/page-template';
import { useTranslation } from 'react-i18next';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { router, usePage } from '@inertiajs/react';
import { useState } from 'react';
import { toast } from '@/components/custom-toast';
interface Plan {
id: number;
name: string;
price: number;
yearly_price: number | null;
duration: string;
description: string | null;
max_users: number;
max_employees: number;
storage_limit: number;
enable_chatgpt: string;
is_trial: string | null;
trial_day: number;
is_plan_enable: string;
is_default: boolean;
}
interface Props {
plan?: Plan;
hasDefaultPlan?: boolean;
otherDefaultPlanExists?: boolean;
}
export default function PlanForm({ plan, hasDefaultPlan = false, otherDefaultPlanExists = false }: Props) {
const { t } = useTranslation();
const { globalSettings } = usePage().props as any;
const [processing, setProcessing] = useState(false);
const isEdit = !!plan;
const [formData, setFormData] = useState({
name: plan?.name || '',
price: plan?.price || 0,
yearly_price: plan?.yearly_price || undefined,
duration: plan?.duration || 'monthly',
description: plan?.description || '',
max_users: plan?.max_users || 0,
max_employees: plan?.max_employees || 0,
storage_limit: plan?.storage_limit || 0,
enable_chatgpt: plan?.enable_chatgpt || 'off',
is_trial: plan?.is_trial || null,
trial_day: plan?.trial_day || 0,
is_plan_enable: plan?.is_plan_enable || 'on',
is_default: plan?.is_default || false,
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSwitchChange = (name: string, checked: boolean) => {
setFormData(prev => ({ ...prev, [name]: checked ? 'on' : 'off' }));
};
const handleDefaultChange = (checked: boolean) => {
setFormData(prev => ({ ...prev, is_default: checked }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setProcessing(true);
if (isEdit) {
router.put(route('plans.update', plan.id), formData, {
onSuccess: () => {
setProcessing(false);
router.get(route('plans.index'));
},
onError: (errors) => {
setProcessing(false);
toast.dismiss();
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to update plan: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
} else {
router.post(route('plans.store'), formData, {
onSuccess: () => {
setProcessing(false);
router.get(route('plans.index'));
},
onError: (errors) => {
setProcessing(false);
toast.dismiss();
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to create plan: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
}
};
return (
<PageTemplate
title={t(isEdit ? "Edit Plan" : "Create Plan")}
description={t(isEdit ? "Update subscription plan details" : "Add a new subscription plan")}
url={isEdit ? route('plans.update', plan.id) : "/plans/create"}
breadcrumbs={[
{ title: t('Dashboard'), href: route('dashboard') },
{ title: t('Plans'), href: route('plans.index') },
{ title: t(isEdit ? 'Edit Plan' : 'Create Plan') }
]}
>
<div className="bg-white rounded-lg shadow p-6">
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<Label htmlFor="name" required>{t("Plan Name")}</Label>
<Input
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="price" required>{t("Monthly Price")}</Label>
<Input
id="price"
name="price"
type="number"
step="0.01"
value={formData.price}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="yearly_price" required>{t("Yearly Price")}</Label>
<Input
id="yearly_price"
name="yearly_price"
type="number"
step="0.01"
value={formData.yearly_price || ''}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="description">{t("Description")}</Label>
<Textarea
id="description"
name="description"
value={formData.description}
onChange={handleChange}
rows={3}
/>
</div>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="max_users" required>{t("Maximum Users")}</Label>
<Input
id="max_users"
name="max_users"
type="number"
value={formData.max_users}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="max_employees" required>{t("Maximum Employees")}</Label>
<Input
id="max_employees"
name="max_employees"
type="number"
value={formData.max_employees}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="storage_limit" required>{t("Storage Limit (GB)")}</Label>
<Input
id="storage_limit"
name="storage_limit"
type="number"
step="0.01"
value={formData.storage_limit}
onChange={handleChange}
required
/>
</div>
<div>
<Label htmlFor="trial_day">{t("Trial Days")}</Label>
<Input
id="trial_day"
name="trial_day"
type="number"
value={formData.trial_day}
onChange={handleChange}
/>
</div>
</div>
</div>
<div className="border rounded-lg p-4 space-y-4">
<h3 className="font-medium">{t("Features")}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center justify-between">
<Label htmlFor="enable_chatgpt">{t("AI Integration")}</Label>
<Switch
id="enable_chatgpt"
checked={formData.enable_chatgpt === 'on'}
onCheckedChange={(checked) => handleSwitchChange('enable_chatgpt', checked)}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="is_trial">{t("Enable Trial")}</Label>
<Switch
id="is_trial"
checked={formData.is_trial === 'on'}
onCheckedChange={(checked) => handleSwitchChange('is_trial', checked)}
/>
</div>
</div>
</div>
<div className="border rounded-lg p-4 space-y-4">
<h3 className="font-medium">{t("Settings")}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center justify-between">
<Label htmlFor="is_plan_enable">{t("Active")}</Label>
<Switch
id="is_plan_enable"
checked={formData.is_plan_enable === 'on'}
onCheckedChange={(checked) => handleSwitchChange('is_plan_enable', checked)}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label htmlFor="is_default">{t("Default Plan")}</Label>
{(isEdit ? !plan?.is_default : hasDefaultPlan) && (
<p className="text-xs text-amber-600 mt-1">
{t("Setting this as default will remove default status from the current default plan.")}
</p>
)}
</div>
<Switch
id="is_default"
checked={formData.is_default}
onCheckedChange={handleDefaultChange}
/>
</div>
</div>
</div>
<div className="flex justify-end gap-3">
<Button
type="button"
variant="outline"
onClick={() => router.get(route('plans.index'))}
>
{t("Cancel")}
</Button>
<Button
type="submit"
disabled={processing}
>
{processing ? t(isEdit ? "Updating..." : "Creating...") : t(isEdit ? "Update Plan" : "Create Plan")}
</Button>
</div>
</form>
</div>
</PageTemplate>
);
}