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 { formatCurrency, mockRevenueData } from '@/data/mockData';
|
||||
import { mockTransactions } from '@/data/mockFinancialData';
|
||||
import { SettlementTable } from '@/features/financials/components/SettlementTable';
|
||||
import { TransactionList } from '@/features/financials/components/TransactionList';
|
||||
import { TaxSettingsSheet } from '@/features/financials/components/TaxSettingsSheet';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Financials() {
|
||||
const [showTaxSettings, setShowTaxSettings] = useState(false);
|
||||
|
||||
const totalRevenue = mockRevenueData.reduce((sum, d) => sum + d.revenue, 0);
|
||||
const totalPayouts = mockRevenueData.reduce((sum, d) => sum + d.payouts, 0);
|
||||
// Calculating platform fee (approx 10% of revenue for mock)
|
||||
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 (
|
||||
<AppLayout
|
||||
title="Financials & Settlements"
|
||||
@@ -73,11 +111,17 @@ export default function Financials() {
|
||||
<div className="neu-card p-6">
|
||||
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
||||
<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
|
||||
<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 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
|
||||
<Wallet className="h-4 w-4 text-muted-foreground group-hover:text-foreground" />
|
||||
</button>
|
||||
@@ -85,6 +129,8 @@ export default function Financials() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TaxSettingsSheet open={showTaxSettings} onOpenChange={setShowTaxSettings} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user