Implement Quick Actions functionality on Financials page (Monthly Report Download, Tax Settings)
This commit is contained in:
172
src/features/financials/components/TaxSettingsSheet.tsx
Normal file
172
src/features/financials/components/TaxSettingsSheet.tsx
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { Loader2, Landmark } from "lucide-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from "@/components/ui/sheet";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const taxSettingsSchema = z.object({
|
||||||
|
gstNumber: z.string().min(15, "GST Number must be 15 characters").max(15, "GST Number must be 15 characters"),
|
||||||
|
panNumber: z.string().min(10, "PAN Number must be 10 characters").max(10, "PAN Number must be 10 characters"),
|
||||||
|
platformFeePercentage: z.string().regex(/^\d+(\.\d{1,2})?$/, "Must be a valid percentage"),
|
||||||
|
taxPercentage: z.string().regex(/^\d+(\.\d{1,2})?$/, "Must be a valid percentage"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof taxSettingsSchema>;
|
||||||
|
|
||||||
|
interface TaxSettingsSheetProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TaxSettingsSheet({ open, onOpenChange }: TaxSettingsSheetProps) {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(taxSettingsSchema),
|
||||||
|
defaultValues: {
|
||||||
|
gstNumber: "27AAAAA0000A1Z5",
|
||||||
|
panNumber: "AAAAA0000A",
|
||||||
|
platformFeePercentage: "12",
|
||||||
|
taxPercentage: "18",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onSubmit(data: FormValues) {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
// Simulate API call
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
|
console.log("Tax settings updated:", data);
|
||||||
|
toast.success("Tax settings updated successfully");
|
||||||
|
|
||||||
|
setIsSubmitting(false);
|
||||||
|
onOpenChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||||
|
<SheetContent className="w-[400px] sm:w-[540px]">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Tax & Platform Settings</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Configure platform fees and tax details.
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 mt-6">
|
||||||
|
|
||||||
|
<div className="bg-secondary/20 p-4 rounded-lg flex items-start gap-4 mb-6">
|
||||||
|
<Landmark className="h-6 w-6 text-primary mt-1" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-foreground">Global Settings</h4>
|
||||||
|
<p className="text-sm text-muted-foreground">These settings apply to all new events and transactions by default.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="platformFeePercentage"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Default Platform Fee (%)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="12" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Percentage of ticket sales taken as platform revenue.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="taxPercentage"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Govt. Tax (GST) (%)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="18" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Applicable GST on platform fees.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="gstNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Company GSTIN</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="27AAAAA0000A1Z5" {...field} className="uppercase" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="panNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Company PAN</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="AAAAA0000A" {...field} className="uppercase" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3 pt-4">
|
||||||
|
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={isSubmitting} className="bg-primary text-primary-foreground">
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Updating...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Save Changes"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,54 @@
|
|||||||
|
|
||||||
import { IndianRupee, TrendingUp, Wallet } from 'lucide-react';
|
import { useState } from 'react';
|
||||||
|
import { IndianRupee, TrendingUp, Wallet, Download } from 'lucide-react';
|
||||||
import { AppLayout } from '@/components/layout/AppLayout';
|
import { AppLayout } from '@/components/layout/AppLayout';
|
||||||
import { formatCurrency, mockRevenueData } from '@/data/mockData';
|
import { formatCurrency, mockRevenueData } from '@/data/mockData';
|
||||||
|
import { mockTransactions } from '@/data/mockFinancialData';
|
||||||
import { SettlementTable } from '@/features/financials/components/SettlementTable';
|
import { SettlementTable } from '@/features/financials/components/SettlementTable';
|
||||||
import { TransactionList } from '@/features/financials/components/TransactionList';
|
import { TransactionList } from '@/features/financials/components/TransactionList';
|
||||||
|
import { TaxSettingsSheet } from '@/features/financials/components/TaxSettingsSheet';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export default function Financials() {
|
export default function Financials() {
|
||||||
|
const [showTaxSettings, setShowTaxSettings] = useState(false);
|
||||||
|
|
||||||
const totalRevenue = mockRevenueData.reduce((sum, d) => sum + d.revenue, 0);
|
const totalRevenue = mockRevenueData.reduce((sum, d) => sum + d.revenue, 0);
|
||||||
const totalPayouts = mockRevenueData.reduce((sum, d) => sum + d.payouts, 0);
|
const totalPayouts = mockRevenueData.reduce((sum, d) => sum + d.payouts, 0);
|
||||||
// Calculating platform fee (approx 10% of revenue for mock)
|
// Calculating platform fee (approx 10% of revenue for mock)
|
||||||
const platformEarnings = totalRevenue * 0.12;
|
const platformEarnings = totalRevenue * 0.12;
|
||||||
|
|
||||||
|
const handleDownloadReport = () => {
|
||||||
|
try {
|
||||||
|
const headers = ['ID', 'Date', 'Type', 'Title', 'Partner', 'Amount', 'Status'];
|
||||||
|
const csvContent = [
|
||||||
|
headers.join(','),
|
||||||
|
...mockTransactions.map(tx => [
|
||||||
|
tx.id,
|
||||||
|
new Date(tx.date).toLocaleDateString(),
|
||||||
|
tx.type,
|
||||||
|
`"${tx.title}"`,
|
||||||
|
`"${tx.partner}"`,
|
||||||
|
tx.amount,
|
||||||
|
tx.status
|
||||||
|
].join(','))
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
link.setAttribute('href', url);
|
||||||
|
link.setAttribute('download', 'financial_report.csv');
|
||||||
|
link.style.visibility = 'hidden';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
|
||||||
|
toast.success("Monthly report downloaded successfully");
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to generate report");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
title="Financials & Settlements"
|
title="Financials & Settlements"
|
||||||
@@ -73,11 +111,17 @@ export default function Financials() {
|
|||||||
<div className="neu-card p-6">
|
<div className="neu-card p-6">
|
||||||
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<button className="w-full text-left px-4 py-3 rounded-lg bg-secondary/50 hover:bg-secondary transition-colors text-sm font-medium flex items-center justify-between group">
|
<button
|
||||||
|
onClick={handleDownloadReport}
|
||||||
|
className="w-full text-left px-4 py-3 rounded-lg bg-secondary/50 hover:bg-secondary transition-colors text-sm font-medium flex items-center justify-between group"
|
||||||
|
>
|
||||||
Download Monthly Report
|
Download Monthly Report
|
||||||
<IndianRupee className="h-4 w-4 text-muted-foreground group-hover:text-foreground" />
|
<Download className="h-4 w-4 text-muted-foreground group-hover:text-foreground" />
|
||||||
</button>
|
</button>
|
||||||
<button className="w-full text-left px-4 py-3 rounded-lg bg-secondary/50 hover:bg-secondary transition-colors text-sm font-medium flex items-center justify-between group">
|
<button
|
||||||
|
onClick={() => setShowTaxSettings(true)}
|
||||||
|
className="w-full text-left px-4 py-3 rounded-lg bg-secondary/50 hover:bg-secondary transition-colors text-sm font-medium flex items-center justify-between group"
|
||||||
|
>
|
||||||
Update Tax Settings
|
Update Tax Settings
|
||||||
<Wallet className="h-4 w-4 text-muted-foreground group-hover:text-foreground" />
|
<Wallet className="h-4 w-4 text-muted-foreground group-hover:text-foreground" />
|
||||||
</button>
|
</button>
|
||||||
@@ -85,6 +129,8 @@ export default function Financials() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TaxSettingsSheet open={showTaxSettings} onOpenChange={setShowTaxSettings} />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user