From 977ab05e6141ddee80a15e16e0ac4d29f9906126 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Sat, 19 Jul 2025 20:19:56 +0530 Subject: [PATCH] created list view interface --- src/components/Expenses/CreateExpense.jsx | 128 ++++++++++++++++++++++ src/components/Expenses/ExpenseList.jsx | 98 +++++++++++++++++ src/components/Expenses/ExpenseSchema.js | 66 +++++++++++ src/data/menuData.json | 6 + src/hooks/useExpense.js | 30 +++++ src/pages/Expense/ExpensePage.jsx | 48 ++++++++ src/repositories/ExpsenseRepository.jsx | 13 +++ src/router/AppRoutes.jsx | 2 + 8 files changed, 391 insertions(+) create mode 100644 src/components/Expenses/CreateExpense.jsx create mode 100644 src/components/Expenses/ExpenseList.jsx create mode 100644 src/components/Expenses/ExpenseSchema.js create mode 100644 src/hooks/useExpense.js create mode 100644 src/pages/Expense/ExpensePage.jsx create mode 100644 src/repositories/ExpsenseRepository.jsx diff --git a/src/components/Expenses/CreateExpense.jsx b/src/components/Expenses/CreateExpense.jsx new file mode 100644 index 00000000..927269a9 --- /dev/null +++ b/src/components/Expenses/CreateExpense.jsx @@ -0,0 +1,128 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { ExpenseSchema } from "./ExpenseSchema"; + +const CreateExpense = () => { + const {} = useForm({ + resolver: zodResolver(ExpenseSchema), + defaultValues: { + projectId: "", + expensesTypeId: "", + paymentModeId: "", + paidById: "", + transactionDate: "", + transactionId: "", + description: "", + location: "", + supplerName: "", + amount: "", + noOfPersons: "", + statusId: "", + billAttachments: [], + }, + }); + return ( +
+

Create New Expense

+
+
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+
+
+ + ); +}; + +export default CreateExpense; diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx new file mode 100644 index 00000000..8fc54a7e --- /dev/null +++ b/src/components/Expenses/ExpenseList.jsx @@ -0,0 +1,98 @@ +import React from 'react' + +const ExpenseList = () => { + return ( +
+
+
+ + + + + + + + + + + + + + + + + +
+
Date
+
+
Expense Type
+
+
Payment mode
+
+
Paid By
+
+ Status + + Amount +
+
+
+
+ ) +} + +export default ExpenseList \ No newline at end of file diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js new file mode 100644 index 00000000..1b5f4bb1 --- /dev/null +++ b/src/components/Expenses/ExpenseSchema.js @@ -0,0 +1,66 @@ +import { z } from 'zod'; + +const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB +const ALLOWED_TYPES = [ + 'application/pdf', + 'image/png', + 'image/jpg', + 'image/jpeg', +]; + +export const ExpenseSchema = z.object({ + projectId: z.string().min(1, { message: "Project is required" }), + expensesTypeId: z.string().min(1, { message: "Expense type is required" }), + paymentModeId: z.string().min(1, { message: "Payment mode is required" }), + paidById: z.string().min(1, { message: "Employee name is required" }), + transactionDate: z.string().min(1, { message: "Date is required" }), + transactionId: z.string().optional(), // if optional, else use .min(1) + description: z.string().min(1, { message: "Description is required" }), + location: z.string().min(1, { message: "Location is required" }), + supplerName: z.string().min(1, { message: "Supplier name is required" }), + amount: z.number().min(1, { message: "Amount must be at least 1" }).max(10000, { message: "Amount must not exceed 10,000" }), + noOfPersons: z.number().min(1, { message: "1 Employee at least required" }), + statusId: z.string().min(1, { message: "Please select status" }), + + billAttachments: z + .array( + z.object({ + fileName: z.string().min(1, { message: "Filename is required" }), + base64Data: z.string().min(1, { message: "File data is required" }), + contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), { + message: "Only PDF, PNG, JPG, or JPEG files are allowed", + }), + fileSize: z.number().max(MAX_FILE_SIZE, { + message: "File size must be less than or equal to 5MB", + }), + description: z.string().optional(), + }) + ) + .nonempty({ message: "At least one file attachment is required" }), +}); + + +let payload= +{ +"projectId": "2618f2ef-2823-11f0-9d9e-bc241163f504", +"expensesTypeId": "dd120bc4-ab0a-45ba-8450-5cd45ff221ca", +"paymentModeId": "ed667353-8eea-4fd1-8750-719405932480", +"paidById": "08dda7d8-014e-443f-858d-a55f4b484bc4", +"transactionDate": "2025-07-12T09:56:54.122Z", +"transactionId": "string", +"description": "string", +"location": "string", +"supplerName": "string", +"amount": 390, +"noOfPersons": 0, +"statusId": "297e0d8f-f668-41b5-bfea-e03b354251c8", +"billAttachments": [ +{ +"fileName": "string", +"base64Data": "string", +"contentType": "string", +"fileSize": 0, +"description": "string" +} +] +} \ No newline at end of file diff --git a/src/data/menuData.json b/src/data/menuData.json index bbd37e2c..d4b2104d 100644 --- a/src/data/menuData.json +++ b/src/data/menuData.json @@ -66,6 +66,12 @@ "available": true, "link": "/gallary" }, + { + "text": "Expense", + "icon": "bx bx-receipt", + "available": true, + "link": "/expenses" + }, { "text": "Administration", "icon": "bx bx-box", diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js new file mode 100644 index 00000000..a2f72cc6 --- /dev/null +++ b/src/hooks/useExpense.js @@ -0,0 +1,30 @@ +import { useMutation } from "@tanstack/react-query" +import ExpenseRepository from "../repositories/ExpsenseRepository" +import showToast from "../services/toastService" + + + +// -------------------Query------------------------------------------------------ +export const useExpenseList = ()=>{ + +} + + + + + +// ---------------------------Mutation--------------------------------------------- + +export const useCreateExpnse =()=>{ + return useMutation({ + mutationFn:(payload)=>{ + await ExpenseRepository.CreateExpense(payload) + }, + onSuccess:(_,variables)=>{ + showToast("Expense Created Successfully","success") + }, + onError:(error)=>{ + showToast(error.message || "Something went wrong please try again !","success") + } + }) +} \ No newline at end of file diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx new file mode 100644 index 00000000..136b2d2f --- /dev/null +++ b/src/pages/Expense/ExpensePage.jsx @@ -0,0 +1,48 @@ +import React, { useState } from "react"; +import ExpenseList from "../../components/Expenses/ExpenseList"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import GlobalModel from "../../components/common/GlobalModel"; +import CreateExpense from "../../components/Expenses/createExpense"; + +const ExpensePage = () => { + const[IsNewExpen,setNewExpense] = useState(false) + + return ( +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + + setNewExpense(false)}> + + +
+ ); +}; + +export default ExpensePage; diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx new file mode 100644 index 00000000..03199212 --- /dev/null +++ b/src/repositories/ExpsenseRepository.jsx @@ -0,0 +1,13 @@ +import { api } from "../utils/axiosClient"; + + +const ExpenseRepository = { + GetExpenseList:()=>api.get("/api/expanse/list"), + GetExpenseDetails:(id)=>api.get(`/api/Expanse/details/${id}`), + CreateExpense:(data)=>api.post("/api/Expanse/create",data), + UpdateExpense:(id)=>api.put(`/api/Expanse/edit/${id}`), + DeleteExpense:(id)=>api.delete(`/api/Expanse/edit/${id}`) + +} + +export default ExpenseRepository; \ No newline at end of file diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 5ce5b6d4..d89191d8 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -38,6 +38,7 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard"; import ProtectedRoute from "./ProtectedRoute"; import Directory from "../pages/Directory/Directory"; import LoginWithOtp from "../pages/authentication/LoginWithOtp"; +import ExpensePage from "../pages/Expense/ExpensePage"; const router = createBrowserRouter( [ @@ -76,6 +77,7 @@ const router = createBrowserRouter( { path: "/activities/task", element: }, { path: "/activities/reports", element: }, { path: "/gallary", element: }, + { path: "/expenses", element: }, { path: "/masters", element: }, { path: "/help/support", element: }, { path: "/help/docs", element: },