Changeset 057453c for src


Ignore:
Timestamp:
02/26/25 10:05:32 (6 weeks ago)
Author:
Naum Shapkarovski <naumshapkarovski@…>
Branches:
main
Children:
299af01
Parents:
5d6f37a
Message:

feat: implement employees

Location:
src
Files:
21 added
2 deleted
36 edited

Legend:

Unmodified
Added
Removed
  • src/api/customer.ts

    r5d6f37a r057453c  
    11import { useMemo } from 'react';
    22// types
    3 import { Customer } from 'mvpmasters-shared';
     3import { Customer } from 'src/schemas';
    44// db
    55import { endpoints, fetcher } from 'src/utils/axios';
  • src/api/invoice.ts

    r5d6f37a r057453c  
    11import { useMemo } from 'react';
    22// types
    3 import { Invoice } from 'mvpmasters-shared';
     3import { Invoice, UpdateInvoice } from 'src/schemas';
    44// db
    55import useSWR from 'swr';
    66import { endpoints, fetcher } from 'src/utils/axios';
     7import axios from 'src/utils/axios';
     8import { mutate } from 'swr';
     9import { useSWRConfig } from 'swr';
    710
    811interface InvoiceFilters {
     
    5154}
    5255
    53 // export function useGetInvoice({ id }: { id: string }) {
    54 //   const collectionName = collections.invoice;
     56export function useGetInvoice({ id }: { id: string }) {
     57  const path = endpoints.invoice;
    5558
    56 //   const { data, isLoading, error, isValidating } = useSWR(
    57 //     [collectionName, id],
    58 //     () => documentFetcher<Invoice>(collectionName, id),
    59 //     {
    60 //       revalidateOnFocus: false,
    61 //     }
    62 //   );
     59  const { data, isLoading, error, isValidating } = useSWR(
     60    `${path}/${id}`,
     61    () => fetcher<Invoice>(`${path}/${id}`),
     62    {
     63      revalidateOnFocus: false,
     64    }
     65  );
    6366
    64 //   const memoizedValue = useMemo(
    65 //     () => ({
    66 //       currentInvoice: data || null,
    67 //       currentInvoiceLoading: isLoading,
    68 //       currentInvoiceError: error,
    69 //       currentInvoiceValidating: isValidating,
    70 //       currentInvoiceEmpty: !isLoading && !data,
    71 //     }),
    72 //     [data, error, isLoading, isValidating]
    73 //   );
     67  const memoizedValue = useMemo(
     68    () => ({
     69      currentInvoice: data || null,
     70      currentInvoiceLoading: isLoading,
     71      currentInvoiceError: error,
     72      currentInvoiceValidating: isValidating,
     73      currentInvoiceEmpty: !isLoading && !data,
     74    }),
     75    [data, error, isLoading, isValidating]
     76  );
    7477
    75 //   return memoizedValue;
    76 // }
     78  return memoizedValue;
     79}
     80
     81// Add this interface for the create invoice payload
     82interface CreateInvoicePayload {
     83  createDate: Date;
     84  dueDate: Date;
     85  items: any[];
     86  invoiceNumber: string;
     87  invoiceFrom: any;
     88  invoiceTo: any;
     89  currency: string;
     90  quantityType: string;
     91  month: string;
     92  status?: string;
     93  taxes?: number;
     94  discount?: number;
     95  totalAmount: number;
     96  pdfRef?: string;
     97}
     98
     99export async function createInvoice(data: CreateInvoicePayload): Promise<Invoice> {
     100  const response = await axios.post<Invoice>(endpoints.invoice, data);
     101
     102  // Mutate the SWR cache to include the new invoice
     103  await mutate(
     104    endpoints.invoice,
     105    (existingInvoices: Invoice[] = []) => [response.data, ...existingInvoices],
     106    false // Set to false to avoid revalidation since we already have the new data
     107  );
     108
     109  return response.data;
     110}
     111
     112export async function updateInvoice(id: string, data: Partial<UpdateInvoice>) {
     113  const response = await axios.patch<Invoice>(`${endpoints.invoice}/${id}`, data);
     114
     115  // Mutate the individual invoice cache
     116  await mutate(`${endpoints.invoice}/${id}`, response.data, false);
     117
     118  // Mutate the invoice list cache
     119  await mutate(
     120    endpoints.invoice,
     121    (existingInvoices: Invoice[] = []) =>
     122      existingInvoices.map((invoice) => (invoice.id === id ? response.data : invoice)),
     123    false
     124  );
     125
     126  return response.data;
     127}
     128
     129export async function deleteInvoice(id: string) {
     130  const response = await axios.delete<Invoice>(`${endpoints.invoice}/${id}`);
     131
     132  // Mutate the invoice list cache to remove the deleted invoice
     133  await mutate(
     134    endpoints.invoice,
     135    (existingInvoices: Invoice[] = []) => existingInvoices.filter((invoice) => invoice.id !== id),
     136    false
     137  );
     138
     139  return response.data;
     140}
     141
     142// Update the useDeleteInvoice hook to use the new implementation
     143export function useDeleteInvoice() {
     144  const deleteInvoiceMutation = async (id: string) => {
     145    try {
     146      await deleteInvoice(id);
     147      return true;
     148    } catch (error) {
     149      console.error('Error deleting invoice:', error);
     150      throw error;
     151    }
     152  };
     153
     154  return { deleteInvoiceMutation };
     155}
  • src/api/service.ts

    r5d6f37a r057453c  
    11import { useMemo } from 'react';
    22// types
    3 import { Service } from 'mvpmasters-shared';
    4 // db
    5 import { collections, collectionFetcher as fetcher } from 'src/lib/firestore';
     3import { Service } from 'src/schemas';
    64// swr
    75import useSWR from 'swr';
     6import { endpoints, fetcher } from 'src/utils/axios';
    87
    98export function useGetServices() {
    10   const collectionName = collections.service;
    11 
    12   const { data, isLoading, error, isValidating } = useSWR(collectionName, fetcher<Service>, {
    13     revalidateOnFocus: false,
    14   });
     9  const { data, isLoading, error, isValidating } = useSWR<Service[]>(
     10    endpoints.service,
     11    () => fetcher<Service[]>(endpoints.service),
     12    {
     13      revalidateOnFocus: false,
     14    }
     15  );
    1516
    1617  const memoizedValue = useMemo(
  • src/app/api/customers/[id]/route.ts

    r5d6f37a r057453c  
    11import { NextRequest, NextResponse } from 'next/server';
    2 import { customerSchema } from 'mvpmasters-shared';
     2import { customerSchema } from 'src/schemas';
    33import prisma from 'src/lib/prisma';
    44import { authenticateRequest } from 'src/lib/auth-middleware';
     
    1414    const validatedData = customerSchema.partial().parse(body);
    1515
    16     const customer = await prisma.customer.update({
    17       where: { id: params.id, userId },
    18       data: validatedData,
     16    const customer = await prisma.client.update({
     17      where: { id: params.id },
     18      data: {
     19        ...validatedData,
     20        bankAccounts: undefined,
     21      },
    1922    });
    2023
  • src/app/api/customers/route.ts

    r5d6f37a r057453c  
    11import { NextRequest, NextResponse } from 'next/server';
    2 import { customerTableFiltersSchema, newCustomerSchema } from 'mvpmasters-shared';
     2import { customerTableFiltersSchema, newCustomerSchema } from 'src/schemas';
    33import prisma from 'src/lib/prisma';
    44import { authenticateRequest } from 'src/lib/auth-middleware';
     5import { CustomerStatus } from '@prisma/client';
    56
    67export async function GET(request: NextRequest) {
     
    2324    const validatedFilters = customerTableFiltersSchema.parse(filters);
    2425
    25     const customers = await prisma.customer.findMany({
     26    const customers = await prisma.client.findMany({
    2627      where: {
    2728        name: { contains: validatedFilters.name, mode: 'insensitive' },
    28         status: validatedFilters.status ? { equals: validatedFilters.status } : undefined,
     29        status: validatedFilters.status
     30          ? { equals: validatedFilters.status as CustomerStatus }
     31          : undefined,
    2932      },
    3033    });
     
    5053    console.log('validatedData', validatedData);
    5154
    52     const customer = await prisma.customer.create({
     55    const customer = await prisma.client.create({
    5356      data: {
    5457        ...validatedData,
  • src/app/api/invoices/[id]/route.ts

    r5d6f37a r057453c  
    11import { NextRequest, NextResponse } from 'next/server';
    2 import { invoiceSchema } from 'mvpmasters-shared';
     2import { invoiceSchema, updateInvoiceSchema } from 'src/schemas';
    33import prisma from 'src/lib/prisma';
    44import { authenticateRequest } from 'src/lib/auth-middleware';
     5import { Prisma } from '@prisma/client';
    56
    6 export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) {
     7export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
    78  try {
    8     // Authenticate the request
     9    // Validate ID format
     10    if (!params.id || !/^[0-9a-fA-F-]+$/.test(params.id)) {
     11      return NextResponse.json({ error: 'Invalid invoice ID format' }, { status: 400 });
     12    }
     13
     14    // Authenticate request
    915    const authResult = await authenticateRequest(request);
    1016    if (authResult instanceof NextResponse) {
     
    1319    const { userId } = authResult;
    1420
    15     const body = await request.json();
    16     const validatedData = invoiceSchema.partial().parse(body);
    17 
    18     const invoice = await prisma.invoice.update({
    19       where: { id: params.id, userId },
    20       data: {
    21         ...validatedData,
    22         items: validatedData.items
    23           ? {
    24               deleteMany: {},
    25               create: validatedData.items,
    26             }
    27           : undefined,
     21    // Fetch invoice with user check
     22    const invoice = await prisma.invoice.findFirst({
     23      where: {
     24        id: params.id,
     25        // invoiceFromId: userId,
    2826      },
    2927      include: {
    30         items: {
    31           include: {
    32             service: true,
    33           },
    34         },
    3528        invoiceFrom: true,
    3629        invoiceTo: true,
     30        items: true,
    3731      },
    3832    });
    3933
     34    if (!invoice) {
     35      return NextResponse.json({ error: 'Invoice not found or access denied' }, { status: 404 });
     36    }
     37
    4038    return NextResponse.json(invoice);
    4139  } catch (error) {
    42     return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
     40    console.error('Error fetching invoice:', error);
     41
     42    if (error instanceof Prisma.PrismaClientKnownRequestError) {
     43      return NextResponse.json({ error: 'Database error occurred' }, { status: 500 });
     44    }
     45
     46    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
    4347  }
    4448}
     49
     50export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) {
     51  try {
     52    // Validate ID format
     53    if (!params.id || !/^[0-9a-fA-F-]+$/.test(params.id)) {
     54      return NextResponse.json({ error: 'Invalid invoice ID format' }, { status: 400 });
     55    }
     56
     57    // Authenticate request
     58    const authResult = await authenticateRequest(request);
     59    if (authResult instanceof NextResponse) {
     60      return authResult;
     61    }
     62    const { userId } = authResult;
     63
     64    // Parse and validate request body
     65    const body = await request.json();
     66
     67    const validation = updateInvoiceSchema.partial().safeParse(body);
     68
     69    if (!validation.success) {
     70      return NextResponse.json(
     71        { error: 'Invalid invoice data', details: validation.error.format() },
     72        { status: 400 }
     73      );
     74    }
     75
     76    // Verify invoice exists and belongs to user
     77    const existingInvoice = await prisma.invoice.findFirst({
     78      where: {
     79        id: params.id,
     80        // invoiceFromId: userId,
     81      },
     82    });
     83
     84    if (!existingInvoice) {
     85      return NextResponse.json({ error: 'Invoice not found or access denied' }, { status: 404 });
     86    }
     87
     88    // Update invoice and related data
     89    const updatedInvoice = await prisma.$transaction(async (tx) => {
     90      // Conditionally delete and recreate items only if they are provided
     91      if (validation.data.items) {
     92        await tx.invoiceItem.deleteMany({
     93          where: { invoiceId: params.id },
     94        });
     95      }
     96
     97      // Update the invoice and create new items if provided
     98      return tx.invoice.update({
     99        where: { id: params.id },
     100        data: {
     101          invoiceNumber: validation.data.invoiceNumber,
     102          createDate: validation.data.createDate,
     103          dueDate: validation.data.dueDate,
     104          status: validation.data.status,
     105          currency: validation.data.currency,
     106          quantityType: validation.data.quantityType,
     107          subTotal: validation.data.subTotal,
     108          month: validation.data.month,
     109          totalAmount: validation.data.totalAmount,
     110          discount: validation.data.discount,
     111          taxes: validation.data.taxes,
     112          pdfRef: validation.data.pdfRef,
     113          invoiceTo: {
     114            update: validation.data.invoiceTo,
     115          },
     116          items: validation.data.items
     117            ? {
     118                create: validation.data.items.map((item) => ({
     119                  ...item,
     120                  service: {
     121                    connect: { id: item.service.id },
     122                  },
     123                })),
     124              }
     125            : undefined,
     126        },
     127        include: {
     128          invoiceFrom: true,
     129          invoiceTo: true,
     130          items: true,
     131        },
     132      });
     133    });
     134
     135    return NextResponse.json(updatedInvoice);
     136  } catch (error) {
     137    console.error('Error updating invoice:', error);
     138
     139    if (error instanceof Prisma.PrismaClientKnownRequestError) {
     140      return NextResponse.json({ error: 'Database error occurred' }, { status: 500 });
     141    }
     142
     143    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
     144  }
     145}
     146
     147export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
     148  try {
     149    // Validate ID format
     150    if (!params.id || !/^[0-9a-fA-F-]+$/.test(params.id)) {
     151      return NextResponse.json({ error: 'Invalid invoice ID format' }, { status: 400 });
     152    }
     153
     154    // Authenticate request
     155    const authResult = await authenticateRequest(request);
     156    if (authResult instanceof NextResponse) {
     157      return authResult;
     158    }
     159    const { userId } = authResult;
     160    console.log('userId', userId);
     161
     162    // Verify invoice exists and belongs to user
     163    const existingInvoice = await prisma.invoice.findFirst({
     164      where: {
     165        id: params.id,
     166      },
     167    });
     168
     169    if (!existingInvoice) {
     170      return NextResponse.json({ error: 'Invoice not found or access denied' }, { status: 404 });
     171    }
     172
     173    // Delete invoice and related items in a transaction
     174    await prisma.$transaction(async (tx) => {
     175      // Delete related items first
     176      await tx.invoiceItem.deleteMany({
     177        where: { invoiceId: params.id },
     178      });
     179
     180      // Delete the invoice
     181      await tx.invoice.delete({
     182        where: { id: params.id },
     183      });
     184    });
     185
     186    return NextResponse.json({ message: 'Invoice deleted successfully' }, { status: 200 });
     187  } catch (error) {
     188    console.error('Error deleting invoice:', error);
     189
     190    if (error instanceof Prisma.PrismaClientKnownRequestError) {
     191      if (error.code === 'P2025') {
     192        return NextResponse.json({ error: 'Invoice not found' }, { status: 404 });
     193      }
     194      return NextResponse.json({ error: 'Database error occurred' }, { status: 500 });
     195    }
     196
     197    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
     198  }
     199}
  • src/app/api/invoices/route.ts

    r5d6f37a r057453c  
    11import { NextRequest, NextResponse } from 'next/server';
    2 import { invoiceSchema, invoiceTableFiltersSchema } from 'mvpmasters-shared';
     2import { createInvoiceSchema, invoiceSchema, invoiceTableFiltersSchema } from 'src/schemas';
    33import prisma from 'src/lib/prisma';
    44import { auth } from 'src/lib/firebase-admin';
     5import { InvoiceStatus } from '@prisma/client';
    56
    67// Helper function to get userId from Authorization header
     
    4142    const invoices = await prisma.invoice.findMany({
    4243      where: {
    43         userId,
    44         status: validatedFilters.status ? { equals: validatedFilters.status } : undefined,
     44        status: validatedFilters.status
     45          ? { equals: validatedFilters.status as InvoiceStatus }
     46          : undefined,
    4547        createDate: {
    46           gte: validatedFilters.startDate,
    47           lte: validatedFilters.endDate,
     48          ...(validatedFilters.startDate && { gte: validatedFilters.startDate }),
     49          ...(validatedFilters.endDate && { lte: validatedFilters.endDate }),
    4850        },
    4951        items:
     
    7779
    7880    const body = await request.json();
    79     const validatedData = invoiceSchema.parse(body);
     81    const validatedData = createInvoiceSchema.parse(body);
     82
     83    const tenant = await prisma.tenant.findUnique({
     84      where: { id: validatedData.invoiceFrom.id },
     85    });
     86
     87    const toCustomer = await prisma.client.findUnique({
     88      where: { id: validatedData.invoiceTo.id },
     89    });
     90
     91    if (!tenant || !toCustomer) {
     92      return NextResponse.json({ error: 'Invoice sender or recipient not found' }, { status: 404 });
     93    }
     94
     95    // Update lastInvoiceNumber in tenant
     96    const updatedTenant = await prisma.tenant.update({
     97      where: { id: tenant.id },
     98      data: {
     99        lastInvoiceNumber: validatedData.invoiceNumber,
     100      },
     101    });
    80102
    81103    const invoice = await prisma.invoice.create({
    82104      data: {
    83         ...validatedData,
    84         userId,
     105        dueDate: validatedData.dueDate,
     106        status: validatedData.status,
     107        currency: validatedData.currency,
     108        quantityType: validatedData.quantityType,
     109        subTotal: validatedData.subTotal,
     110        createDate: validatedData.createDate,
     111        month: validatedData.month,
     112        discount: validatedData.discount,
     113        taxes: validatedData.taxes,
     114        totalAmount: validatedData.totalAmount,
     115        invoiceNumber: validatedData.invoiceNumber,
     116        invoiceFromId: tenant.id,
     117        invoiceToId: toCustomer.id,
    85118        items: {
    86           create: validatedData.items,
     119          create: validatedData.items.map((item) => ({
     120            title: item.title,
     121            price: item.price,
     122            total: item.total,
     123            quantity: item.quantity,
     124            description: item.description,
     125            service: {
     126              connect: { id: item.service.id },
     127            },
     128          })),
    87129        },
    88130      },
     
    100142    return NextResponse.json(invoice, { status: 201 });
    101143  } catch (error) {
     144    console.error(error);
    102145    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
    103146  }
  • src/layouts/dashboard/config-navigation.tsx

    r5d6f37a r057453c  
    5353        items: [
    5454          {
    55             title: 'banking',
     55            title: 'dashboard',
    5656            path: paths.dashboard.banking,
    5757            icon: ICONS.banking,
     
    8585            ],
    8686          },
     87          {
     88            title: 'Employees',
     89            path: paths.dashboard.employee.list,
     90            icon: <SvgColor src="/assets/icons/navbar/ic_user.svg" />,
     91            children: [
     92              { title: 'list', path: paths.dashboard.employee.list },
     93              { title: 'create', path: paths.dashboard.employee.new },
     94            ],
     95          },
    8796        ],
    8897      },
  • src/lib/auth-middleware.ts

    r5d6f37a r057453c  
    44export interface AuthenticatedRequest extends NextRequest {
    55  userId: string;
     6  tenantId: string;
    67}
    78
    89export async function authenticateRequest(
    910  request: NextRequest
    10 ): Promise<{ userId: string } | NextResponse> {
     11): Promise<{ userId: string; tenantId: string } | NextResponse> {
    1112  // Get the authorization header
    1213  const authHeader = request.headers.get('Authorization');
     
    2223    const decodedToken = await auth.verifyIdToken(token);
    2324    const userId = decodedToken.uid;
     25    const tenantId = decodedToken.customClaims?.tenantId || 'cm7bwtjy80000pb0m5qenk8am';
    2426
    25     if (!userId) {
     27    if (!userId || !tenantId) {
    2628      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    2729    }
    2830
    29     return { userId };
     31    return { userId, tenantId };
    3032  } catch (error) {
    3133    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  • src/routes/paths.ts

    r5d6f37a r057453c  
    3434      list: `${ROOTS.DASHBOARD}/customer/list`,
    3535    },
     36    employee: {
     37      root: '/dashboard/employee',
     38      list: '/dashboard/employee/list',
     39      new: '/dashboard/employee/new',
     40      edit: (id: string) => `/dashboard/employee/${id}/edit`,
     41    },
    3642  },
    3743};
  • src/sections/company/company-item.tsx

    r5d6f37a r057453c  
    44import Stack, { StackProps } from '@mui/material/Stack';
    55// types
    6 import { Customer } from 'mvpmasters-shared';
     6import { Customer } from 'src/schemas';
    77// components
    88import Label from 'src/components/label';
  • src/sections/company/company-list-dialog.tsx

    r5d6f37a r057453c  
    99import ListItemButton, { listItemButtonClasses } from '@mui/material/ListItemButton';
    1010// types
    11 import { Customer } from 'mvpmasters-shared';
     11import { Customer } from 'src/schemas';
    1212// components
    1313import Iconify from 'src/components/iconify';
    1414import SearchNotFound from 'src/components/search-not-found';
    1515import { createFullAddress } from 'src/utils/create-full-address';
     16import { Tenant } from 'src/schemas';
    1617
    1718// ----------------------------------------------------------------------
     
    1920type Props = {
    2021  title?: string;
    21   list: Customer[];
     22  tenant?: Tenant;
     23  list?: Customer[];
    2224  action?: React.ReactNode;
    2325  //
     
    2628  //
    2729  selected: (selectedId: string) => boolean;
    28   onSelect: (address: Customer | null) => void;
     30  onSelect: (address: Customer | Tenant | null) => void;
    2931};
    3032
    3133export default function AddressListDialog({
    3234  title = 'Address Book',
     35  tenant,
    3336  list,
    3437  action,
     
    4346
    4447  const dataFiltered = applyFilter({
    45     inputData: list,
     48    inputData: list || (tenant ? [tenant] : []),
    4649    query: searchCompany,
    4750  });
     
    5457
    5558  const handleSelectCompany = useCallback(
    56     (company: Customer | null) => {
     59    (company: Customer | Tenant | null) => {
    5760      onSelect(company);
    5861      setSearchCompany('');
     
    156159// ----------------------------------------------------------------------
    157160
    158 function applyFilter({ inputData, query }: { inputData: Customer[]; query: string }) {
     161function applyFilter({ inputData, query }: { inputData: Tenant[] | Customer[]; query: string }) {
    159162  if (query) {
    160163    return inputData.filter(
  • src/sections/invoice/invoice-details.tsx

    r5d6f37a r057453c  
    1818import { fCurrency } from 'src/utils/format-number';
    1919// types
    20 import { Invoice } from 'mvpmasters-shared';
     20import { Invoice } from 'src/schemas';
    2121// components
    2222import Label from 'src/components/label';
     
    2828import { getQuantityType } from 'src/utils/get-invoice-quantity-type';
    2929import InvoiceToolbar from './invoice-toolbar';
     30import { updateInvoice } from 'src/api/invoice';
    3031
    3132// ----------------------------------------------------------------------
     
    5960  const handleChangeStatus = useCallback(
    6061    async (event: React.ChangeEvent<HTMLInputElement>) => {
    61       await updateDocument(collections.invoice, invoice.id, { status: event.target.value });
     62      // await updateDocument(collections.invoice, invoice.id, { status: event.target.value });
     63      await updateInvoice(invoice.id, {
     64        status: event.target.value as 'draft' | 'processing' | 'pending' | 'overdue' | 'paid',
     65      });
    6266      mutate([collections.invoice, invoice.id]);
    6367    },
     
    240244            {invoice.invoiceTo.name}
    241245            <br />
    242             {!!invoice.invoiceTo.companyId && (
     246            {!!invoice.invoiceTo.companyNumber && (
    243247              <>
    244                 Company ID: {invoice.invoiceTo.companyId}
     248                Company ID: {invoice.invoiceTo.companyNumber}
    245249                <br />
    246250              </>
     
    257261              Date Issued
    258262            </Typography>
    259             {fDate(invoice.createDate.toDate())}
     263            {fDate(invoice.createDate)}
    260264          </Stack>
    261265
     
    264268              Due Date
    265269            </Typography>
    266             {fDate(invoice.dueDate.toDate())}
     270            {fDate(invoice.dueDate)}
    267271          </Stack>
    268272        </Box>
  • src/sections/invoice/invoice-ee-pdf.tsx

    r5d6f37a r057453c  
    55import { fCurrency } from 'src/utils/format-number';
    66// types
    7 import { Invoice } from 'mvpmasters-shared';
     7import { Invoice } from 'src/schemas';
    88import { createFullAddress } from 'src/utils/create-full-address';
    99import { getQuantityType } from 'src/utils/get-invoice-quantity-type';
     
    229229          <View style={styles.col6}>
    230230            <Text style={[styles.subtitle1, styles.mb4]}>Date Issued</Text>
    231             <Text style={styles.body2}>{fDate(createDate.toDate())}</Text>
     231            <Text style={styles.body2}>{fDate(createDate)}</Text>
    232232          </View>
    233233          <View style={styles.col6}>
    234234            <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text>
    235             <Text style={styles.body2}>{fDate(dueDate.toDate())}</Text>
     235            <Text style={styles.body2}>{fDate(dueDate)}</Text>
    236236          </View>
    237237        </View>
  • src/sections/invoice/invoice-mk-pdf.tsx

    r5d6f37a r057453c  
    55import { fCurrency } from 'src/utils/format-number';
    66// types
    7 import { Invoice } from 'mvpmasters-shared';
     7import { Invoice } from 'src/schemas';
    88import { createFullAddress } from 'src/utils/create-full-address';
    99import { getQuantityType } from 'src/utils/get-invoice-quantity-type';
     
    179179          <View style={styles.col6}>
    180180            <Text style={[styles.subtitle1, styles.mb4]}>Date Issued</Text>
    181             <Text style={styles.body2}>{fDate(createDate.toDate())}</Text>
     181            <Text style={styles.body2}>{fDate(createDate)}</Text>
    182182          </View>
    183183          <View style={styles.col6}>
    184184            <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text>
    185             <Text style={styles.body2}>{fDate(dueDate.toDate())}</Text>
     185            <Text style={styles.body2}>{fDate(dueDate)}</Text>
    186186          </View>
    187187        </View>
  • src/sections/invoice/invoice-new-edit-address.tsx

    r5d6f37a r057453c  
    77import Typography from '@mui/material/Typography';
    88// hooks
    9 import { useGetSettings } from 'src/api/settings';
     9import { useGetTenant } from 'src/api/tenant';
    1010import { useBoolean } from 'src/hooks/use-boolean';
    1111import { useResponsive } from 'src/hooks/use-responsive';
     
    3232  const { invoiceFrom, invoiceTo } = values;
    3333
    34   const { settings } = useGetSettings();
     34  const { settings: tenant } = useGetTenant();
    3535  const { customers } = useGetCustomers();
    3636
     
    100100      </Stack>
    101101
    102       {settings && (
     102      {tenant && (
    103103        <CompanyListDialog
    104104          title="Companies"
     
    106106          onClose={from.onFalse}
    107107          selected={(selectedId: string) => invoiceFrom?.id === selectedId}
    108           onSelect={(company) => setValue('invoiceFrom', company)}
    109           list={[settings?.company, settings?.['company-ee']]}
     108          onSelect={(tenant) => setValue('invoiceFrom', tenant)}
     109          tenant={tenant}
    110110          action={
    111111            <Button
  • src/sections/invoice/invoice-new-edit-details.tsx

    r5d6f37a r057453c  
    1414import { fCurrency } from 'src/utils/format-number';
    1515// types
    16 import { InvoiceItem } from 'mvpmasters-shared';
     16import { InvoiceItem } from 'src/schemas';
    1717// components
    1818import Iconify from 'src/components/iconify';
     
    7575  const handleSelectService = useCallback(
    7676    (index: number, option: string) => {
    77       setValue(
    78         `items[${index}].price`,
    79         invoiceServices.find((service) => service.id === option)?.price?.[
    80           values.quantityType?.toLowerCase() as 'sprint' | 'hour' | 'month'
    81         ]
    82       );
    83       setValue(
    84         `items[${index}].total`,
    85         values.items.map((item: InvoiceItem) => item.quantity * item.price)[index]
    86       );
     77      const service = invoiceServices.find((service) => service.id === option);
     78      if (!service) return;
     79
     80      const quantityType = values.quantityType?.toLowerCase();
     81      let price = 0;
     82
     83      switch (quantityType) {
     84        case 'sprint':
     85          price = service.sprint;
     86          break;
     87        case 'hour':
     88          price = service.hour;
     89          break;
     90        case 'month':
     91          price = service.month;
     92          break;
     93        default:
     94          price = 0;
     95      }
     96
     97      setValue(`items[${index}].price`, price);
     98      setValue(`items[${index}].total`, values.items[index].quantity * price);
    8799    },
    88100    [setValue, values.items, values.quantityType, invoiceServices]
  • src/sections/invoice/invoice-new-edit-form.tsx

    r5d6f37a r057453c  
    1111import { useRouter } from 'src/routes/hooks';
    1212// types
    13 import { Invoice } from 'mvpmasters-shared';
     13import { CreateInvoice, Invoice } from 'src/schemas';
    1414// hooks
    1515import { useBoolean } from 'src/hooks/use-boolean';
     
    2020import uploadToFirebaseStorage from 'src/utils/upload-to-firebase-storage';
    2121import { pdf } from '@react-pdf/renderer';
    22 import { useGetSettings } from 'src/api/settings';
    23 import {
    24   collections,
    25   documents,
    26   firestoreBatch,
    27   generateId,
    28   updateDocument,
    29 } from 'src/lib/firestore';
     22import { useGetTenant } from 'src/api/tenant';
     23import { collections, generateId, updateDocument } from 'src/lib/firestore';
    3024import { useGetServices } from 'src/api/service';
    3125import { mutate } from 'swr';
    32 import { Timestamp } from 'firebase/firestore';
    3326import InvoiceNewEditStatusDate from './invoice-new-edit-status-date';
    3427import InvoiceNewEditAddress from './invoice-new-edit-address';
    3528import InvoiceNewEditDetails from './invoice-new-edit-details';
    3629import InvoicePDF from './invoice-pdf';
     30import { createInvoice, updateInvoice } from 'src/api/invoice';
    3731
    3832// ----------------------------------------------------------------------
     
    5852];
    5953
     54interface InvoiceItem {
     55  service: string | null;
     56  title: string;
     57  price: number;
     58  total: number;
     59  quantity: number;
     60  description: string;
     61}
     62
    6063export default function InvoiceNewEditForm({ isCopy, currentInvoice }: Props) {
    6164  const router = useRouter();
     
    6972    description: Yup.string().required('Description is required'),
    7073    service: Yup.string().nullable(),
    71     quantity: Yup.number().required('Quantity is required').min(0.5, 'Quantity must be at least 1'),
     74    quantity: Yup.number()
     75      .required('Quantity is required')
     76      .min(0.5, 'Quantity must be at least 0.5'),
    7277    price: Yup.number().required('Price is required').min(0, 'Price must be at least 0'),
    7378    total: Yup.number().required('Total is required').min(0, 'Total must be at least 0'),
     
    9196      .required('Quantity type is required'),
    9297    month: Yup.string().oneOf(monthNames).required('Month is required'),
    93     status: Yup.string().required(),
     98    status: Yup.string().oneOf(['draft', 'processing', 'pending', 'overdue', 'paid']).required(),
    9499    totalAmount: Yup.number().required(),
    95100    // not required
     
    103108
    104109  const { services: invoiceServices } = useGetServices();
    105   const { settings, settingsEmpty, settingsLoading } = useGetSettings();
     110  console.log('invoiceServices', invoiceServices);
     111  const { settings: tenant, settingsEmpty, settingsLoading } = useGetTenant();
    106112
    107113  const defaultValues = useMemo(
     
    110116        !isCopy && currentInvoice?.invoiceNumber
    111117          ? currentInvoice?.invoiceNumber
    112           : incrementInvoiceNumber(settings?.invoice?.lastInvoiceNumber),
    113       createDate: currentInvoice?.createDate?.toDate() || new Date(),
    114       dueDate: currentInvoice?.dueDate?.toDate() || null,
     118          : incrementInvoiceNumber(tenant?.lastInvoiceNumber),
     119      createDate: currentInvoice?.createDate ? new Date(currentInvoice.createDate) : new Date(),
     120      dueDate: currentInvoice?.dueDate
     121        ? new Date(currentInvoice.dueDate)
     122        : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
    115123      invoiceFrom: currentInvoice?.invoiceFrom || null,
    116124      invoiceTo: currentInvoice?.invoiceTo || null,
     
    138146      totalAmount: currentInvoice?.totalAmount || 0,
    139147    }),
    140     [currentInvoice, isCopy, settings]
     148    [currentInvoice, isCopy, tenant]
    141149  );
    142150
     
    164172
    165173    try {
    166       // generate collection id
    167174      const id = generateId(collections.invoice);
     175
     176      // Ensure dates are valid Date objects
     177      const createDate =
     178        data.createDate instanceof Date ? data.createDate : new Date(data.createDate);
     179      const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate);
     180
     181      const currentTime = new Date();
     182      createDate.setHours(
     183        currentTime.getHours(),
     184        currentTime.getMinutes(),
     185        currentTime.getSeconds()
     186      );
    168187
    169188      // attach serivce details
     
    173192      }));
    174193
    175       const currentTime = new Date();
    176       const createDateWithCurrentTime = new Date(data.createDate); // This creates a date object using the date from data.createDate
    177       // Set the time of createDateWithCurrentTime to the current hour, minutes, and seconds
    178       createDateWithCurrentTime.setHours(
    179         currentTime.getHours(),
    180         currentTime.getMinutes(),
    181         currentTime.getSeconds()
    182       );
    183 
    184194      // transform data
    185       const writeData = {
     195      const writeData: CreateInvoice = {
    186196        ...data,
    187         invoiceNumber: incrementInvoiceNumber(settings?.invoice.lastInvoiceNumber),
     197        invoiceNumber: incrementInvoiceNumber(tenant?.lastInvoiceNumber),
    188198        status: 'draft',
    189         createDate: Timestamp.fromDate(createDateWithCurrentTime),
    190         dueDate: Timestamp.fromDate(new Date(data.dueDate)),
    191         items,
     199        createDate,
     200        dueDate,
     201        items: items.filter((item) => item.service !== null) as CreateInvoice['items'],
     202        subTotal: data.totalAmount - (data.taxes || 0) - (data.discount || 0),
     203        month: data.month as Invoice['month'],
     204        invoiceFrom: data.invoiceFrom!,
     205        invoiceTo: data.invoiceTo!,
    192206      };
    193207
     
    199213
    200214      // write to DB
    201       await firestoreBatch([
    202         {
    203           docPath: `${collections.invoice}/${id}`,
    204           type: 'set',
    205           data: {
    206             ...writeData,
    207             pdfRef: storagePath,
    208           },
    209         },
    210         {
    211           docPath: `${collections.settings}/${documents.settingsInvoice}`,
    212           type: 'set',
    213           data: { lastInvoiceNumber: writeData.invoiceNumber },
    214         },
    215       ]);
     215      // await firestoreBatch([
     216      //   {
     217      //     docPath: `${collections.invoice}/${id}`,
     218      //     type: 'set',
     219      //     data: {
     220      //       ...writeData,
     221      //       pdfRef: storagePath,
     222      //     },
     223      //   },
     224      //   {
     225      //     docPath: `${collections.settings}/${documents.settingsInvoice}`,
     226      //     type: 'set',
     227      //     data: { lastInvoiceNumber: writeData.invoiceNumber },
     228      //   },
     229      // ]);
     230
     231      await createInvoice({ ...writeData, pdfRef: storagePath });
    216232
    217233      loadingSave.onFalse();
     
    229245    try {
    230246      if (currentInvoice) {
     247        // Ensure dates are valid Date objects
     248        const createDate =
     249          data.createDate instanceof Date ? data.createDate : new Date(data.createDate);
     250        const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate);
     251
    231252        // attach serivce details
    232253        const items = data.items.map((item) => ({
     
    238259        const writeData = {
    239260          ...data,
    240           createDate: Timestamp.fromDate(new Date(data.createDate)),
    241           dueDate: Timestamp.fromDate(new Date(data.dueDate)),
     261          createDate,
     262          dueDate,
    242263          items,
     264          invoiceFrom: data.invoiceFrom!,
     265          invoiceTo: data.invoiceTo!,
    243266        };
    244267
     
    250273
    251274        // update DB
    252         await updateDocument(collections.invoice, currentInvoice.id, {
     275        // await updateDocument(collections.invoice, currentInvoice.id, {
     276        //   ...writeData,
     277        //   pdfRef: storagePath,
     278        // });
     279
     280        await updateInvoice(currentInvoice.id, {
    253281          ...writeData,
    254282          pdfRef: storagePath,
     283          status: data.status as 'draft' | 'processing' | 'pending' | 'overdue' | 'paid',
     284          month: data.month as
     285            | 'January'
     286            | 'February'
     287            | 'March'
     288            | 'April'
     289            | 'May'
     290            | 'June'
     291            | 'July'
     292            | 'August'
     293            | 'September'
     294            | 'October'
     295            | 'November'
     296            | 'December',
     297          items: items.filter((item) => item.service !== null) as {
     298            service: { id: string; month: number; name: string; sprint: number; hour: number };
     299            title: string;
     300            price: number;
     301            total: number;
     302            quantity: number;
     303            description: string;
     304          }[],
    255305        });
    256306
    257307        // mutate current data
    258         mutate([collections.invoice, currentInvoice.id]);
     308        // mutate([collections.invoice, currentInvoice.id]);
    259309      } else {
    260310        // generate collection id
    261311        const id = generateId(collections.invoice);
     312
     313        // Ensure dates are valid Date objects
     314        const createDate =
     315          data.createDate instanceof Date ? data.createDate : new Date(data.createDate);
     316        const dueDate = data.dueDate instanceof Date ? data.dueDate : new Date(data.dueDate);
    262317
    263318        // attach serivce details
     
    270325        const writeData = {
    271326          ...data,
    272           createDate: Timestamp.fromDate(new Date(data.createDate)),
    273           dueDate: Timestamp.fromDate(new Date(data.dueDate)),
     327          createDate,
     328          dueDate,
    274329          items,
     330          invoiceFrom: data.invoiceFrom!,
     331          invoiceTo: data.invoiceTo!,
    275332        };
    276333
     
    282339
    283340        // write to DB
    284         await firestoreBatch([
    285           {
    286             docPath: `${collections.invoice}/${id}`,
    287             type: 'set',
    288             data: {
    289               ...writeData,
    290               pdfRef: storagePath,
    291             },
    292           },
    293           {
    294             docPath: `${collections.settings}/${documents.settingsInvoice}`,
    295             type: 'set',
    296             data: { lastInvoiceNumber: writeData.invoiceNumber },
    297           },
    298         ]);
     341        // await firestoreBatch([
     342        //   {
     343        //     docPath: `${collections.invoice}/${id}`,
     344        //     type: 'set',
     345        //     data: {
     346        //       ...writeData,
     347        //       pdfRef: storagePath,
     348        //     },
     349        //   },
     350        //   {
     351        //     docPath: `${collections.settings}/${documents.settingsInvoice}`,
     352        //     type: 'set',
     353        //     data: { lastInvoiceNumber: writeData.invoiceNumber },
     354        //   },
     355        // ]);
     356        await createInvoice({ ...writeData, pdfRef: storagePath });
    299357
    300358        reset();
     
    314372    <FormProvider methods={methods}>
    315373      <Card>
    316         {settings?.company && <InvoiceNewEditAddress />}
     374        {!!tenant && <InvoiceNewEditAddress />}
    317375
    318376        <InvoiceNewEditStatusDate isCopy={isCopy} />
  • src/sections/invoice/invoice-new-edit-status-date.tsx

    r5d6f37a r057453c  
    77import { RHFSelect, RHFTextField } from 'src/components/hook-form';
    88// api
    9 import { useGetSettings } from 'src/api/settings';
     9// import { useGetTenant } from 'src/api/settings';
    1010// utils
    1111import { incrementInvoiceNumber } from 'src/utils/increment-invoice-number';
     
    3333  const values = watch();
    3434
    35   const { settings } = useGetSettings();
     35  // const { settings } = useGetTenant();
    3636
    3737  return (
  • src/sections/invoice/invoice-pdf.tsx

    r5d6f37a r057453c  
    1 import { Invoice } from 'mvpmasters-shared';
     1import { Invoice } from 'src/schemas';
    22import InvoiceEEPDF from './invoice-ee-pdf';
    33import InvoiceMKPDF from './invoice-mk-pdf';
  • src/sections/invoice/invoice-table-filters-result.tsx

    r5d6f37a r057453c  
    66import Stack, { StackProps } from '@mui/material/Stack';
    77// types
    8 import { InvoiceTableFilters, InvoiceTableFilterValue } from 'mvpmasters-shared';
     8import { InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas';
    99// components
    1010import Iconify from 'src/components/iconify';
  • src/sections/invoice/invoice-table-row.tsx

    r5d6f37a r057453c  
    1616import { fNumber } from 'src/utils/format-number';
    1717// types
    18 import { Invoice } from 'mvpmasters-shared';
     18import { Invoice } from 'src/schemas';
    1919// components
    2020import Label from 'src/components/label';
     
    6060  } = row;
    6161
     62  console.log(createDate);
     63
    6264  const confirmSend = useBoolean();
    6365  const confirmDelete = useBoolean();
     
    105107        <TableCell>
    106108          <ListItemText
    107             primary={format(createDate.toDate(), 'dd MMM yyyy')}
    108             secondary={format(createDate.toDate(), 'p')}
     109            primary={format(new Date(createDate), 'dd MMM yyyy')}
     110            secondary={format(new Date(createDate), 'p')}
    109111            primaryTypographyProps={{ typography: 'body2', noWrap: true }}
    110112            secondaryTypographyProps={{
     
    118120        <TableCell>
    119121          <ListItemText
    120             primary={format(dueDate.toDate(), 'dd MMM yyyy')}
    121             secondary={format(dueDate.toDate(), 'p')}
     122            primary={format(new Date(dueDate), 'dd MMM yyyy')}
     123            secondary={format(new Date(dueDate), 'p')}
    122124            primaryTypographyProps={{ typography: 'body2', noWrap: true }}
    123125            secondaryTypographyProps={{
  • src/sections/invoice/invoice-table-toolbar.tsx

    r5d6f37a r057453c  
    77import InputAdornment from '@mui/material/InputAdornment';
    88// types
    9 import { InvoiceTableFilters, InvoiceTableFilterValue } from 'mvpmasters-shared';
     9import { InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas';
    1010// components
    1111import Iconify from 'src/components/iconify';
  • src/sections/invoice/invoice-toolbar.tsx

    r5d6f37a r057453c  
    1818import { useBoolean } from 'src/hooks/use-boolean';
    1919// types
    20 import { Invoice } from 'mvpmasters-shared';
     20import { Invoice } from 'src/schemas';
    2121// components
    2222import Iconify from 'src/components/iconify';
  • src/sections/invoice/mail-compose.tsx

    r5d6f37a r057453c  
    1313import Iconify from 'src/components/iconify';
    1414import Editor from 'src/components/editor';
    15 import { Invoice } from 'mvpmasters-shared';
     15import { Invoice } from 'src/schemas';
    1616import FormProvider from 'src/components/hook-form/form-provider';
    1717import { RHFTextField } from 'src/components/hook-form';
  • src/sections/invoice/view/invoice-edit-view.tsx

    r5d6f37a r057453c  
    2323
    2424  const { currentInvoice } = useGetInvoice({ id });
     25  console.log('currentInvoice', currentInvoice);
    2526
    2627  return (
  • src/sections/invoice/view/invoice-list-view.tsx

    r5d6f37a r057453c  
    3636} from 'src/components/table';
    3737// types
    38 import {
    39   Invoice,
    40   InvoiceStatus,
    41   InvoiceTableFilters,
    42   InvoiceTableFilterValue,
    43 } from 'mvpmasters-shared';
     38import { Invoice, InvoiceStatus, InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas';
    4439//
    4540import deleteFromFirebaseStorage from 'src/utils/delete-from-firebase-storage';
    4641// fetch
    47 import { useGetInvoices } from 'src/api/invoice';
     42import { useDeleteInvoice, useGetInvoices } from 'src/api/invoice';
    4843import { collections, removeDocument } from 'src/lib/firestore';
    4944import { mutate } from 'swr';
     
    163158  const [filters, setFilters] = useState(defaultFilters);
    164159
    165   const { invoices: tableData } = useGetInvoices({
    166     where: [['createDate', '>=', filters.startDate]],
    167     orderBy: 'createDate',
    168     direction: 'desc',
    169   });
     160  const { invoices: tableData } = useGetInvoices({ startDate: filters.startDate?.toISOString() });
    170161
    171162  const invoiceMutationKey = useMemo(
     
    269260  );
    270261
     262  const { deleteInvoiceMutation } = useDeleteInvoice();
     263
    271264  const handleDeleteRow = useCallback(
    272265    async (invoice: Invoice) => {
     
    275268        orderBy: 'createDate',
    276269        direction: 'desc',
    277       }); // Get the same params as used in useGetInvoices
    278 
    279       // Optimistically update the cache before the deletion
    280       // mutate(
    281       //   [collections.invoice, serializedParams],
    282       //   (invoices: Invoice[] = []) => invoices.filter((row) => row.id !== invoice.id),
    283       //   false
    284       // );
    285 
    286       await removeDocument(collections.invoice, invoice.id);
     270      });
     271
     272      await deleteInvoiceMutation(invoice.id);
    287273      await deleteFromFirebaseStorage(
    288274        `invoices/${invoice.invoiceTo.name}/${invoice.id}-${invoice.invoiceNumber}.pdf`
    289275      );
    290276
    291       // Optionally, rollback optimistic update or refetch data
    292277      mutate(invoiceMutationKey);
    293278    },
     
    672657      inputData = inputData.filter(
    673658        (invoice) =>
    674           fTimestamp(invoice.createDate.toMillis()) >= fTimestamp(startDate) &&
    675           fTimestamp(invoice.createDate.toMillis()) <= fTimestamp(endDate)
     659          fTimestamp(invoice.createDate.getTime()) >= fTimestamp(startDate.getTime()) &&
     660          fTimestamp(invoice.createDate.getTime()) <= fTimestamp(endDate.getTime())
    676661      );
    677662    }
  • src/sections/user/customer-new-edit-form.tsx

    r5d6f37a r057453c  
    1818import { useRouter } from 'src/routes/hooks';
    1919// types
    20 import { Customer, NewCustomer, newCustomerSchema } from 'mvpmasters-shared';
     20import { Customer, NewCustomer, newCustomerSchema } from 'src/schemas';
    2121// components
    2222import Label from 'src/components/label';
  • src/sections/user/customer-quick-edit-form.tsx

    r5d6f37a r057453c  
    1414import DialogContent from '@mui/material/DialogContent';
    1515// types
    16 import { Customer, customerSchema } from 'mvpmasters-shared';
     16import { Customer, customerSchema } from 'src/schemas';
    1717// assets
    1818import { countries } from 'src/assets/data';
  • src/sections/user/customer-table-filters-result.tsx

    r5d6f37a r057453c  
    99// components
    1010import Iconify from 'src/components/iconify';
    11 import { CustomerTableFilterValue, CustomerTableFilters } from 'mvpmasters-shared';
     11import { CustomerTableFilterValue, CustomerTableFilters } from 'src/schemas';
    1212
    1313// ----------------------------------------------------------------------
  • src/sections/user/customer-table-row.tsx

    r5d6f37a r057453c  
    99import { useBoolean } from 'src/hooks/use-boolean';
    1010// types
    11 import { Customer } from 'mvpmasters-shared';
     11import { Customer } from 'src/schemas';
    1212// components
    1313import Label from 'src/components/label';
  • src/sections/user/customer-table-toolbar.tsx

    r5d6f37a r057453c  
    1212import Select, { SelectChangeEvent } from '@mui/material/Select';
    1313// types
    14 import { CustomerTableFilters, CustomerTableFilterValue } from 'mvpmasters-shared';
     14import { CustomerTableFilters, CustomerTableFilterValue } from 'src/schemas';
    1515// components
    1616import Iconify from 'src/components/iconify';
  • src/sections/user/view/customer-list-view.tsx

    r5d6f37a r057453c  
    3434} from 'src/components/table';
    3535// types
    36 import { Customer, CustomerTableFilters, CustomerTableFilterValue } from 'mvpmasters-shared';
     36import { Customer, CustomerTableFilters, CustomerTableFilterValue } from 'src/schemas';
    3737//
    3838import { useGetCustomers } from 'src/api/customer';
  • src/utils/axios.ts

    r5d6f37a r057453c  
    4545
    4646export const endpoints = {
    47   invoice: '/api/invoice',
     47  invoice: '/api/invoices',
    4848  customer: '/api/customers',
     49  tenant: '/api/tenant',
     50  service: '/api/services',
     51  employee: '/api/employees',
    4952};
  • src/utils/create-full-address.ts

    r5d6f37a r057453c  
    1 import { Address } from 'mvpmasters-shared';
     1import { Address } from 'src/schemas';
    22
    33export function createFullAddress(data: Address): string {
  • src/utils/get-invoice-quantity-type.ts

    r5d6f37a r057453c  
    1 import { Invoice } from 'mvpmasters-shared';
     1import { Invoice } from 'src/schemas';
    22
    33export const getQuantityType = (invoice: Invoice) => {
Note: See TracChangeset for help on using the changeset viewer.