586 lines
28 KiB
TypeScript
586 lines
28 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { Switch } from '@/components/ui/switch';
|
||
import { Textarea } from '@/components/ui/textarea';
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from '@/components/ui/table';
|
||
import { FileText, Database, Bell, Loader2, Plus, Trash2, Save } from 'lucide-react';
|
||
import {
|
||
getForm16Config,
|
||
putForm16Config,
|
||
type Form16AdminConfig as Form16ConfigType,
|
||
type Form16NotificationItem,
|
||
type Form16Notification26AsItem,
|
||
} from '@/services/adminApi';
|
||
import { toast } from 'sonner';
|
||
|
||
function isValidEmail(s: string): boolean {
|
||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s.trim());
|
||
}
|
||
|
||
interface ViewerTableProps {
|
||
emails: string[];
|
||
onAdd: (email: string) => void;
|
||
onRemove: (email: string) => void;
|
||
placeholder?: string;
|
||
}
|
||
|
||
function ViewerTable({ emails, onAdd, onRemove, placeholder }: ViewerTableProps) {
|
||
const [input, setInput] = useState('');
|
||
const add = () => {
|
||
const e = input.trim().toLowerCase();
|
||
if (!e) return;
|
||
if (!isValidEmail(e)) {
|
||
toast.error('Please enter a valid email address');
|
||
return;
|
||
}
|
||
if (emails.includes(e)) {
|
||
toast.error('This email is already in the list');
|
||
return;
|
||
}
|
||
onAdd(e);
|
||
setInput('');
|
||
};
|
||
return (
|
||
<div className="space-y-3">
|
||
<div className="flex gap-2">
|
||
<Input
|
||
type="email"
|
||
placeholder={placeholder ?? 'e.g., user@royalenfield.com'}
|
||
value={input}
|
||
onChange={(e) => setInput(e.target.value)}
|
||
onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), add())}
|
||
className="flex-1"
|
||
/>
|
||
<Button type="button" variant="outline" size="sm" onClick={add} className="shrink-0 gap-1">
|
||
<Plus className="w-4 h-4" />
|
||
Add
|
||
</Button>
|
||
</div>
|
||
{emails.length > 0 ? (
|
||
<div className="rounded-md border">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Email</TableHead>
|
||
<TableHead className="w-[80px] text-right">Actions</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{emails.map((email) => (
|
||
<TableRow key={email}>
|
||
<TableCell className="font-medium">{email}</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => onRemove(email)}
|
||
className="text-destructive hover:text-destructive"
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground py-4 text-center rounded-md border border-dashed">
|
||
No viewers added. Add emails above or leave empty to allow all RE users with access.
|
||
</p>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function defaultNotif(enabled: boolean, template: string): Form16NotificationItem {
|
||
return { enabled, template };
|
||
}
|
||
|
||
const default26AsNotif = (): Form16Notification26AsItem => ({
|
||
enabled: true,
|
||
templateRe: '26AS data has been added. Please review and use for matching dealer Form 16 submissions.',
|
||
templateDealers: 'New 26AS data has been uploaded. You can now submit your Form 16 for the relevant quarter if you haven’t already.',
|
||
});
|
||
|
||
export function Form16AdminConfig() {
|
||
const [loading, setLoading] = useState(true);
|
||
const [saving, setSaving] = useState(false);
|
||
const [submissionViewerEmails, setSubmissionViewerEmails] = useState<string[]>([]);
|
||
const [twentySixAsViewerEmails, setTwentySixAsViewerEmails] = useState<string[]>([]);
|
||
const [reminderEnabled, setReminderEnabled] = useState(true);
|
||
const [reminderDays, setReminderDays] = useState(7);
|
||
|
||
const [notification26AsDataAdded, setNotification26AsDataAdded] = useState<Form16Notification26AsItem>(default26AsNotif());
|
||
const [notificationForm16SuccessCreditNote, setNotificationForm16SuccessCreditNote] = useState<Form16NotificationItem>(defaultNotif(true, 'Form 16 submitted successfully. Credit note: [CreditNoteRef].'));
|
||
const [notificationForm16Unsuccessful, setNotificationForm16Unsuccessful] = useState<Form16NotificationItem>(defaultNotif(true, 'Form 16 submission was unsuccessful. Issue: [Issue]. Please review.'));
|
||
const [alertSubmitForm16Enabled, setAlertSubmitForm16Enabled] = useState(true);
|
||
const [alertSubmitForm16FrequencyDays, setAlertSubmitForm16FrequencyDays] = useState(0);
|
||
const [alertSubmitForm16FrequencyHours, setAlertSubmitForm16FrequencyHours] = useState(24);
|
||
const [alertSubmitForm16RunAtTime, setAlertSubmitForm16RunAtTime] = useState('09:00');
|
||
const [alertSubmitForm16Template, setAlertSubmitForm16Template] = useState('Please submit your Form 16 at your earliest. [Name], due date: [DueDate].');
|
||
const [reminderNotificationEnabled, setReminderNotificationEnabled] = useState(true);
|
||
const [reminderFrequencyDays, setReminderFrequencyDays] = useState(0);
|
||
const [reminderFrequencyHours, setReminderFrequencyHours] = useState(12);
|
||
const [reminderRunAtTime, setReminderRunAtTime] = useState('10:00');
|
||
const [reminderNotificationTemplate, setReminderNotificationTemplate] = useState('Reminder: Form 16 submission is pending. [Name], [Request ID]. Please review.');
|
||
|
||
useEffect(() => {
|
||
let mounted = true;
|
||
getForm16Config()
|
||
.then((config: Form16ConfigType) => {
|
||
if (!mounted) return;
|
||
setSubmissionViewerEmails(config.submissionViewerEmails ?? []);
|
||
setTwentySixAsViewerEmails(config.twentySixAsViewerEmails ?? []);
|
||
setReminderEnabled(config.reminderEnabled ?? true);
|
||
setReminderDays(typeof config.reminderDays === 'number' ? config.reminderDays : 7);
|
||
if (config.notification26AsDataAdded) {
|
||
const n = config.notification26AsDataAdded as Form16Notification26AsItem & { template?: string };
|
||
setNotification26AsDataAdded({
|
||
enabled: n.enabled ?? true,
|
||
templateRe: n.templateRe ?? n.template ?? default26AsNotif().templateRe,
|
||
templateDealers: n.templateDealers ?? default26AsNotif().templateDealers,
|
||
});
|
||
}
|
||
if (config.notificationForm16SuccessCreditNote) setNotificationForm16SuccessCreditNote(config.notificationForm16SuccessCreditNote);
|
||
if (config.notificationForm16Unsuccessful) setNotificationForm16Unsuccessful(config.notificationForm16Unsuccessful);
|
||
setAlertSubmitForm16Enabled(config.alertSubmitForm16Enabled ?? true);
|
||
setAlertSubmitForm16FrequencyDays(config.alertSubmitForm16FrequencyDays ?? 0);
|
||
setAlertSubmitForm16FrequencyHours(config.alertSubmitForm16FrequencyHours ?? 24);
|
||
setAlertSubmitForm16RunAtTime(config.alertSubmitForm16RunAtTime !== undefined && config.alertSubmitForm16RunAtTime !== null ? config.alertSubmitForm16RunAtTime : '09:00');
|
||
setAlertSubmitForm16Template(config.alertSubmitForm16Template ?? 'Please submit your Form 16 at your earliest. [Name], due date: [DueDate].');
|
||
setReminderNotificationEnabled(config.reminderNotificationEnabled ?? true);
|
||
setReminderFrequencyDays(config.reminderFrequencyDays ?? 0);
|
||
setReminderFrequencyHours(config.reminderFrequencyHours ?? 12);
|
||
setReminderRunAtTime(config.reminderRunAtTime !== undefined && config.reminderRunAtTime !== null ? config.reminderRunAtTime : '10:00');
|
||
setReminderNotificationTemplate(config.reminderNotificationTemplate ?? 'Reminder: Form 16 submission is pending. [Name], [Request ID]. Please review.');
|
||
})
|
||
.catch(() => {
|
||
if (mounted) toast.error('Failed to load Form 16 configuration');
|
||
})
|
||
.finally(() => {
|
||
if (mounted) setLoading(false);
|
||
});
|
||
return () => {
|
||
mounted = false;
|
||
};
|
||
}, []);
|
||
|
||
const handleSave = async () => {
|
||
setSaving(true);
|
||
try {
|
||
await putForm16Config({
|
||
submissionViewerEmails,
|
||
twentySixAsViewerEmails,
|
||
reminderEnabled,
|
||
reminderDays: Math.max(1, Math.min(365, reminderDays)) || 7,
|
||
notification26AsDataAdded,
|
||
notificationForm16SuccessCreditNote,
|
||
notificationForm16Unsuccessful,
|
||
alertSubmitForm16Enabled,
|
||
alertSubmitForm16FrequencyDays: Math.max(0, Math.min(365, alertSubmitForm16FrequencyDays)),
|
||
alertSubmitForm16FrequencyHours: Math.max(0, Math.min(168, alertSubmitForm16FrequencyHours)),
|
||
alertSubmitForm16RunAtTime: alertSubmitForm16RunAtTime ?? '',
|
||
alertSubmitForm16Template,
|
||
reminderNotificationEnabled,
|
||
reminderFrequencyDays: Math.max(0, Math.min(365, reminderFrequencyDays)),
|
||
reminderFrequencyHours: Math.max(0, Math.min(168, reminderFrequencyHours)),
|
||
reminderRunAtTime: reminderRunAtTime ?? '',
|
||
reminderNotificationTemplate,
|
||
});
|
||
toast.success('Form 16 configuration saved');
|
||
} catch {
|
||
toast.error('Failed to save Form 16 configuration');
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center py-12">
|
||
<Loader2 className="w-8 h-8 animate-spin text-re-green" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Page header */}
|
||
<div>
|
||
<h2 className="text-2xl font-bold tracking-tight">Form 16 Administration</h2>
|
||
<p className="text-muted-foreground mt-1">
|
||
Configure Form 16 access, who can view submission data and 26AS, and notification settings.
|
||
</p>
|
||
</div>
|
||
|
||
{/* Summary cards */}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">Submission data viewers (RE)</p>
|
||
<p className="text-2xl font-semibold">{submissionViewerEmails.length}</p>
|
||
<p className="text-xs text-muted-foreground mt-0.5">Who can see Form 16 submissions</p>
|
||
</div>
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">26AS viewers (RE)</p>
|
||
<p className="text-2xl font-semibold">{twentySixAsViewerEmails.length}</p>
|
||
<p className="text-xs text-muted-foreground mt-0.5">Who can see 26AS page</p>
|
||
</div>
|
||
<div className="p-4 bg-green-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">Reminders to dealers</p>
|
||
<p className="text-2xl font-semibold text-green-600">{reminderEnabled ? 'On' : 'Off'}</p>
|
||
<p className="text-xs text-muted-foreground mt-0.5">Pending Form 16 reminder schedule</p>
|
||
</div>
|
||
<div className="p-4 bg-purple-50 rounded-lg">
|
||
<p className="text-sm text-muted-foreground">Email / in-app notifications</p>
|
||
<p className="text-2xl font-semibold text-purple-600">
|
||
{[
|
||
notification26AsDataAdded?.enabled,
|
||
notificationForm16SuccessCreditNote?.enabled,
|
||
notificationForm16Unsuccessful?.enabled,
|
||
alertSubmitForm16Enabled,
|
||
reminderNotificationEnabled,
|
||
].filter(Boolean).length}{' '}
|
||
/ 5 enabled
|
||
</p>
|
||
<p className="text-xs text-muted-foreground mt-0.5">To dealers and RE as per rules below</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Submission data viewers */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<FileText className="w-5 h-5" />
|
||
Submission data – who can see
|
||
</CardTitle>
|
||
<CardDescription>
|
||
Users with these email addresses can see Form 16 submission data (and the Form 16 menu in the sidebar). Use the <strong>exact login email</strong> of each user (the same email they use to sign in). Leave the list empty to allow all RE users with Form 16 access.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<ViewerTable
|
||
emails={submissionViewerEmails}
|
||
onAdd={(email) => setSubmissionViewerEmails((prev) => [...prev, email].sort())}
|
||
onRemove={(email) => setSubmissionViewerEmails((prev) => prev.filter((e) => e !== email))}
|
||
placeholder="e.g., user@royalenfield.com"
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 26AS viewers */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Database className="w-5 h-5" />
|
||
26AS page and button – who can see
|
||
</CardTitle>
|
||
<CardDescription>
|
||
Users with these email addresses can see the 26AS page and 26AS menu item. Use the <strong>exact login email</strong> of each user. Leave empty to allow all RE users.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<ViewerTable
|
||
emails={twentySixAsViewerEmails}
|
||
onAdd={(email) => setTwentySixAsViewerEmails((prev) => [...prev, email].sort())}
|
||
onRemove={(email) => setTwentySixAsViewerEmails((prev) => prev.filter((e) => e !== email))}
|
||
placeholder="e.g., user@royalenfield.com"
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Notifications and reminders (simple toggles) */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Bell className="w-5 h-5" />
|
||
Reminder schedule (for dealers)
|
||
</CardTitle>
|
||
<CardDescription>
|
||
When reminders are enabled, dealers with pending Form 16 for a quarter are reminded at this interval. Set how often (in days) the system may send them a reminder to submit Form 16.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<Label htmlFor="reminder-enabled">Enable reminders to dealers</Label>
|
||
<Switch
|
||
id="reminder-enabled"
|
||
checked={reminderEnabled}
|
||
onCheckedChange={setReminderEnabled}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2 max-w-xs">
|
||
<Label htmlFor="reminder-days">Remind dealers every (days)</Label>
|
||
<Input
|
||
id="reminder-days"
|
||
type="number"
|
||
min={1}
|
||
max={365}
|
||
value={reminderDays}
|
||
onChange={(e) => setReminderDays(parseInt(e.target.value, 10) || 7)}
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Notification Configuration – Form 16 events */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Email and in-app notifications</CardTitle>
|
||
<CardDescription>
|
||
Configure who receives each notification, what triggers it, and when it is sent. Templates support placeholders such as [Name], [Request ID], [DueDate].
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
<div className="space-y-3">
|
||
<h4 className="font-medium">Form 16 notifications – recipient and trigger</h4>
|
||
|
||
{/* 26AS data added – separate message for RE users and for dealers */}
|
||
<div className="flex items-start justify-between gap-4 p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex-1 min-w-0 space-y-3">
|
||
<p className="font-medium">26AS data added</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
<span className="font-medium text-foreground">Sent to:</span> RE users who can view 26AS, and separately to all dealers. <span className="font-medium text-foreground">When:</span> As soon as new 26AS data is uploaded.
|
||
</p>
|
||
<div className="space-y-2">
|
||
<div>
|
||
<Label className="text-xs text-muted-foreground">Message to RE users</Label>
|
||
<Textarea
|
||
rows={2}
|
||
value={notification26AsDataAdded.templateRe ?? ''}
|
||
onChange={(e) => setNotification26AsDataAdded((prev) => ({ ...prev, templateRe: e.target.value }))}
|
||
className="resize-none text-sm mt-1"
|
||
placeholder="e.g. 26AS data has been added. Please review..."
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-muted-foreground">Message to dealers</Label>
|
||
<Textarea
|
||
rows={2}
|
||
value={notification26AsDataAdded.templateDealers ?? ''}
|
||
onChange={(e) => setNotification26AsDataAdded((prev) => ({ ...prev, templateDealers: e.target.value }))}
|
||
className="resize-none text-sm mt-1"
|
||
placeholder="e.g. New 26AS data has been uploaded. You can submit Form 16..."
|
||
/>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">Placeholders: [Name], [Request ID]</p>
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={notification26AsDataAdded.enabled}
|
||
onCheckedChange={(enabled) => setNotification26AsDataAdded((prev) => ({ ...prev, enabled }))}
|
||
/>
|
||
</div>
|
||
|
||
{/* Successful Form 16 with credit note */}
|
||
<div className="flex items-start justify-between gap-4 p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium">Form 16 success – credit note issued</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
<span className="font-medium text-foreground">Sent to:</span> The dealer who submitted the Form 16. <span className="font-medium text-foreground">When:</span> Immediately after their submission is matched with 26AS and a credit note is generated.
|
||
</p>
|
||
<div className="mt-2">
|
||
<Textarea
|
||
rows={2}
|
||
value={notificationForm16SuccessCreditNote.template ?? ''}
|
||
onChange={(e) => setNotificationForm16SuccessCreditNote((prev) => ({ ...prev, template: e.target.value }))}
|
||
className="resize-none text-sm"
|
||
placeholder="Message template..."
|
||
/>
|
||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [CreditNoteRef], [Request ID]</p>
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={notificationForm16SuccessCreditNote.enabled}
|
||
onCheckedChange={(enabled) => setNotificationForm16SuccessCreditNote((prev) => ({ ...prev, enabled }))}
|
||
/>
|
||
</div>
|
||
|
||
{/* Unsuccessful Form 16 */}
|
||
<div className="flex items-start justify-between gap-4 p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium">Form 16 unsuccessful (mismatch or issue)</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
<span className="font-medium text-foreground">Sent to:</span> The dealer who submitted. <span className="font-medium text-foreground">When:</span> When their submission fails (e.g. value mismatch with 26AS, duplicate, or validation error) so they can correct and resubmit.
|
||
</p>
|
||
<div className="mt-2">
|
||
<Textarea
|
||
rows={2}
|
||
value={notificationForm16Unsuccessful.template ?? ''}
|
||
onChange={(e) => setNotificationForm16Unsuccessful((prev) => ({ ...prev, template: e.target.value }))}
|
||
className="resize-none text-sm"
|
||
placeholder="Message template..."
|
||
/>
|
||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [Issue], [Request ID]</p>
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={notificationForm16Unsuccessful.enabled}
|
||
onCheckedChange={(enabled) => setNotificationForm16Unsuccessful((prev) => ({ ...prev, enabled }))}
|
||
/>
|
||
</div>
|
||
|
||
{/* Alert to submit Form 16 (auto, configurable) */}
|
||
<div className="flex items-start justify-between gap-4 p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium">Alert – submit Form 16 (to dealers who haven’t submitted)</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
<span className="font-medium text-foreground">Sent to:</span> Dealers who have not yet submitted Form 16 for the current FY. <span className="font-medium text-foreground">When:</span> Daily at the time below (server timezone). All settings are API-driven from this config.
|
||
</p>
|
||
<div className="mt-2 space-y-2">
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="alert-run-at" className="text-sm whitespace-nowrap">Run daily at (optional):</Label>
|
||
<Input
|
||
id="alert-run-at"
|
||
type="time"
|
||
value={alertSubmitForm16RunAtTime}
|
||
onChange={(e) => setAlertSubmitForm16RunAtTime(e.target.value)}
|
||
className="w-28"
|
||
/>
|
||
{alertSubmitForm16RunAtTime ? (
|
||
<Button type="button" variant="ghost" size="sm" className="text-muted-foreground" onClick={() => setAlertSubmitForm16RunAtTime('')}>
|
||
Clear
|
||
</Button>
|
||
) : null}
|
||
</div>
|
||
<span className="text-xs text-muted-foreground">24h, server TZ. Leave empty to disable daily run.</span>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="alert-freq-days" className="text-sm whitespace-nowrap">Frequency (days):</Label>
|
||
<Input
|
||
id="alert-freq-days"
|
||
type="number"
|
||
min={0}
|
||
max={365}
|
||
value={alertSubmitForm16FrequencyDays}
|
||
onChange={(e) => setAlertSubmitForm16FrequencyDays(Math.max(0, parseInt(e.target.value, 10) || 0))}
|
||
className="w-20"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="alert-freq-hours" className="text-sm whitespace-nowrap">Hours:</Label>
|
||
<Input
|
||
id="alert-freq-hours"
|
||
type="number"
|
||
min={0}
|
||
max={168}
|
||
value={alertSubmitForm16FrequencyHours}
|
||
onChange={(e) => setAlertSubmitForm16FrequencyHours(Math.max(0, parseInt(e.target.value, 10) || 0))}
|
||
className="w-20"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<Textarea
|
||
rows={2}
|
||
value={alertSubmitForm16Template}
|
||
onChange={(e) => setAlertSubmitForm16Template(e.target.value)}
|
||
className="resize-none text-sm mt-1"
|
||
placeholder="Message template for alert to dealers..."
|
||
/>
|
||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [DueDate], [Request ID]</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={alertSubmitForm16Enabled}
|
||
onCheckedChange={setAlertSubmitForm16Enabled}
|
||
/>
|
||
</div>
|
||
|
||
{/* Reminder notification (pending Form 16) */}
|
||
<div className="flex items-start justify-between gap-4 p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium">Reminder – pending Form 16 (to dealers)</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
<span className="font-medium text-foreground">Sent to:</span> Dealers who have at least one open Form 16 submission without a credit note. <span className="font-medium text-foreground">When:</span> Daily at the time below (server timezone). All settings are API-driven from this config.
|
||
</p>
|
||
<div className="mt-2 space-y-2">
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="reminder-run-at" className="text-sm whitespace-nowrap">Run daily at (optional):</Label>
|
||
<Input
|
||
id="reminder-run-at"
|
||
type="time"
|
||
value={reminderRunAtTime}
|
||
onChange={(e) => setReminderRunAtTime(e.target.value)}
|
||
className="w-28"
|
||
/>
|
||
{reminderRunAtTime ? (
|
||
<Button type="button" variant="ghost" size="sm" className="text-muted-foreground" onClick={() => setReminderRunAtTime('')}>
|
||
Clear
|
||
</Button>
|
||
) : null}
|
||
</div>
|
||
<span className="text-xs text-muted-foreground">24h, server TZ. Leave empty to disable daily run.</span>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="reminder-freq-days" className="text-sm whitespace-nowrap">Frequency (days):</Label>
|
||
<Input
|
||
id="reminder-freq-days"
|
||
type="number"
|
||
min={0}
|
||
max={365}
|
||
value={reminderFrequencyDays}
|
||
onChange={(e) => setReminderFrequencyDays(Math.max(0, parseInt(e.target.value, 10) || 0))}
|
||
className="w-20"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Label htmlFor="reminder-freq-hours" className="text-sm whitespace-nowrap">Hours:</Label>
|
||
<Input
|
||
id="reminder-freq-hours"
|
||
type="number"
|
||
min={0}
|
||
max={168}
|
||
value={reminderFrequencyHours}
|
||
onChange={(e) => setReminderFrequencyHours(Math.max(0, parseInt(e.target.value, 10) || 0))}
|
||
className="w-20"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<Textarea
|
||
rows={2}
|
||
value={reminderNotificationTemplate}
|
||
onChange={(e) => setReminderNotificationTemplate(e.target.value)}
|
||
className="resize-none text-sm mt-1"
|
||
placeholder="Message template for reminder to dealers..."
|
||
/>
|
||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [Request ID], [Status], [TAT]</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={reminderNotificationEnabled}
|
||
onCheckedChange={setReminderNotificationEnabled}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<div className="flex justify-end">
|
||
<Button onClick={handleSave} disabled={saving} className="bg-re-green hover:bg-re-green/90 gap-2">
|
||
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
||
Save Form 16 configuration
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|