Autosave: 20260313-161733
This commit is contained in:
parent
7b829087d9
commit
0e02d4ba52
@ -141,10 +141,10 @@ module.exports = class Pay_typesDBApi {
|
|||||||
if (data.pay_method !== undefined) updatePayload.pay_method = data.pay_method;
|
if (data.pay_method !== undefined) updatePayload.pay_method = data.pay_method;
|
||||||
|
|
||||||
|
|
||||||
if (data.hourly_rate !== undefined) updatePayload.hourly_rate = data.hourly_rate;
|
if (data.hourly_rate !== undefined) updatePayload.hourly_rate = data.hourly_rate === "" ? null : data.hourly_rate;
|
||||||
|
|
||||||
|
|
||||||
if (data.commission_rate !== undefined) updatePayload.commission_rate = data.commission_rate;
|
if (data.commission_rate !== undefined) updatePayload.commission_rate = data.commission_rate === "" ? null : data.commission_rate;
|
||||||
|
|
||||||
|
|
||||||
if (data.active !== undefined) updatePayload.active = data.active;
|
if (data.active !== undefined) updatePayload.active = data.active;
|
||||||
|
|||||||
@ -44,6 +44,7 @@ const job_chemical_usagesRoutes = require('./routes/job_chemical_usages');
|
|||||||
const payroll_runsRoutes = require('./routes/payroll_runs');
|
const payroll_runsRoutes = require('./routes/payroll_runs');
|
||||||
|
|
||||||
const payroll_line_itemsRoutes = require('./routes/payroll_line_items');
|
const payroll_line_itemsRoutes = require('./routes/payroll_line_items');
|
||||||
|
const reportsRoutes = require("./routes/reports");
|
||||||
|
|
||||||
|
|
||||||
const getBaseUrl = (url) => {
|
const getBaseUrl = (url) => {
|
||||||
|
|||||||
35
backend/src/routes/reports.js
Normal file
35
backend/src/routes/reports.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const passport = require('passport');
|
||||||
|
const db = require('../db/models');
|
||||||
|
const { wrapAsync } = require('../helpers');
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
|
||||||
|
router.post('/payroll', passport.authenticate('jwt', { session: false }), wrapAsync(async (req, res) => {
|
||||||
|
const { startDate, endDate, employeeId } = req.body;
|
||||||
|
|
||||||
|
const where = {};
|
||||||
|
if (startDate || endDate) {
|
||||||
|
where.createdAt = {};
|
||||||
|
if (startDate) where.createdAt[Op.gte] = new Date(startDate);
|
||||||
|
if (endDate) where.createdAt[Op.lte] = new Date(endDate);
|
||||||
|
}
|
||||||
|
if (employeeId) {
|
||||||
|
where.employeeId = employeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineItems = await db.payroll_line_items.findAll({
|
||||||
|
where,
|
||||||
|
include: [{ model: db.users, as: 'employee' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = lineItems.reduce((acc, item) => {
|
||||||
|
acc.totalGrossPay += parseFloat(item.gross_pay || 0);
|
||||||
|
acc.totalHours += parseFloat(item.total_hours || 0);
|
||||||
|
return acc;
|
||||||
|
}, { totalGrossPay: 0, totalHours: 0 });
|
||||||
|
|
||||||
|
res.json({ lineItems, summary });
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
const db = require('../db/models');
|
const db = require('../db/models');
|
||||||
const Job_logsDBApi = require('../db/api/job_logs');
|
const Job_logsDBApi = require('../db/api/job_logs');
|
||||||
|
const CustomersDBApi = require('../db/api/customers');
|
||||||
const processFile = require("../middlewares/upload");
|
const processFile = require("../middlewares/upload");
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
@ -7,16 +8,23 @@ const axios = require('axios');
|
|||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = class Job_logsService {
|
module.exports = class Job_logsService {
|
||||||
static async create(data, currentUser) {
|
static async create(data, currentUser) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
try {
|
try {
|
||||||
|
let customerId = data.customer;
|
||||||
|
|
||||||
|
// If customer is a string and not a UUID, try to find or create
|
||||||
|
if (typeof customerId === 'string' && customerId.length > 0 && !customerId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
|
||||||
|
let customer = await db.customers.findOne({ where: { name: customerId }, transaction });
|
||||||
|
if (!customer) {
|
||||||
|
customer = await CustomersDBApi.create({ name: customerId }, { currentUser, transaction });
|
||||||
|
}
|
||||||
|
customerId = customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
await Job_logsDBApi.create(
|
await Job_logsDBApi.create(
|
||||||
data,
|
{ ...data, customer: customerId },
|
||||||
{
|
{
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
@ -78,10 +86,21 @@ module.exports = class Job_logsService {
|
|||||||
'job_logsNotFound',
|
'job_logsNotFound',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let customerId = data.customer;
|
||||||
|
|
||||||
|
// If customer is a string and not a UUID, try to find or create
|
||||||
|
if (typeof customerId === 'string' && customerId.length > 0 && !customerId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
|
||||||
|
let customer = await db.customers.findOne({ where: { name: customerId }, transaction });
|
||||||
|
if (!customer) {
|
||||||
|
customer = await CustomersDBApi.create({ name: customerId }, { currentUser, transaction });
|
||||||
|
}
|
||||||
|
customerId = customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
const updatedJob_logs = await Job_logsDBApi.update(
|
const updatedJob_logs = await Job_logsDBApi.update(
|
||||||
id,
|
id,
|
||||||
data,
|
{ ...data, customer: customerId },
|
||||||
{
|
{
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
@ -131,8 +150,4 @@ module.exports = class Job_logsService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import React, {useEffect, useRef} from 'react'
|
import React, {useEffect, useRef, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
|
||||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||||
import BaseDivider from './BaseDivider'
|
import BaseDivider from './BaseDivider'
|
||||||
import BaseIcon from './BaseIcon'
|
import BaseIcon from './BaseIcon'
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { ReactNode, useEffect } from 'react'
|
import React, { ReactNode, useEffect , useState } from 'react'
|
||||||
import { useState } from 'react'
|
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||||
import menuAside from '../menuAside'
|
import menuAside from '../menuAside'
|
||||||
|
|||||||
@ -12,11 +12,13 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
href: '/log-work',
|
href: '/log-work',
|
||||||
label: 'Log Work',
|
label: 'Log Work',
|
||||||
icon: icon.mdiPencil,
|
icon: icon.mdiPencil,
|
||||||
|
permissions: 'CREATE_JOB_LOGS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/my-logs',
|
href: '/my-logs',
|
||||||
label: 'My Logs',
|
label: 'My Logs',
|
||||||
icon: icon.mdiViewList,
|
icon: icon.mdiViewList,
|
||||||
|
permissions: 'READ_JOB_LOGS'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -108,9 +110,45 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/payroll_line_items/payroll_line_items-list',
|
href: '/payroll_line_items/payroll_line_items-list',
|
||||||
label: 'Payroll line items',
|
label: 'Payroll line items',
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
},
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
},
|
||||||
icon: 'mdiFileDocumentOutline' in icon ? icon['mdiFileDocumentOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiFileDocumentOutline' in icon ? icon['mdiFileDocumentOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
},
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/reports',
|
||||||
|
label: 'Payroll Reports',
|
||||||
|
icon: icon.mdiChartBar,
|
||||||
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
permissions: 'READ_PAYROLL_LINE_ITEMS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -129,4 +167,4 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default menuAside
|
export default menuAside
|
||||||
@ -7,7 +7,42 @@ import SectionMain from '../components/SectionMain';
|
|||||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
|
|
||||||
|
import { Field, Form, Formik } from 'formik';
|
||||||
|
import FormField from '../components/FormField';
|
||||||
|
import BaseDivider from '../components/BaseDivider';
|
||||||
|
import BaseButtons from '../components/BaseButtons';
|
||||||
|
import BaseButton from '../components/BaseButton';
|
||||||
|
import { SelectField } from '../components/SelectField';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { create } from '../stores/job_logs/job_logsSlice';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
|
||||||
const LogWorkPage = () => {
|
const LogWorkPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
work_date: new Date().toISOString().slice(0, 16),
|
||||||
|
employee: currentUser?.id || '',
|
||||||
|
customer: '',
|
||||||
|
hours_conducted: '',
|
||||||
|
client_paid: '',
|
||||||
|
workers_comp_class: 'roof',
|
||||||
|
pay_type: '',
|
||||||
|
vehicle: '',
|
||||||
|
odometer_start: '',
|
||||||
|
odometer_end: '',
|
||||||
|
job_address: '',
|
||||||
|
status: 'submitted',
|
||||||
|
notes_to_admin: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (data: any) => {
|
||||||
|
await dispatch(create(data));
|
||||||
|
await router.push('/my-logs');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@ -18,7 +53,51 @@ const LogWorkPage = () => {
|
|||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
<p>Work log form goes here.</p>
|
<Formik initialValues={initialValues} onSubmit={(values) => handleSubmit(values)}>
|
||||||
|
<Form>
|
||||||
|
<FormField label="Work Date">
|
||||||
|
<Field type="datetime-local" name="work_date" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Customer" labelFor="customer">
|
||||||
|
<Field name="customer" id="customer" placeholder="Enter customer name" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Hours Conducted">
|
||||||
|
<Field type="number" name="hours_conducted" placeholder="Hours" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Client Paid">
|
||||||
|
<Field type="number" name="client_paid" placeholder="Amount" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Worker's Comp Class" labelFor="workers_comp_class">
|
||||||
|
<Field name="workers_comp_class" id="workers_comp_class" component="select">
|
||||||
|
<option value="roof">Roof</option>
|
||||||
|
<option value="ladder">Ladder</option>
|
||||||
|
<option value="ground">Ground</option>
|
||||||
|
</Field>
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Pay Type" labelFor="pay_type">
|
||||||
|
<Field name="pay_type" id="pay_type" component={SelectField} options={[]} itemRef={'pay_types'} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Vehicle" labelFor="vehicle">
|
||||||
|
<Field name="vehicle" id="vehicle" component={SelectField} options={[]} itemRef={'vehicles'} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Odometer Start">
|
||||||
|
<Field type="number" name="odometer_start" placeholder="Start" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Odometer End">
|
||||||
|
<Field type="number" name="odometer_end" placeholder="End" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Job Address">
|
||||||
|
<Field name="job_address" placeholder="Address" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Notes to Admin" hasTextareaHeight>
|
||||||
|
<Field name="notes_to_admin" as="textarea" placeholder="Notes..." />
|
||||||
|
</FormField>
|
||||||
|
<BaseDivider />
|
||||||
|
<BaseButtons>
|
||||||
|
<BaseButton type="submit" color="info" label="Submit Work Log" />
|
||||||
|
</BaseButtons>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,13 +1,27 @@
|
|||||||
import { mdiViewList, mdiChartTimelineVariant } from '@mdi/js';
|
import { mdiViewList, mdiChartTimelineVariant } from '@mdi/js';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import CardBox from '../components/CardBox';
|
import type { ReactElement } from 'react';
|
||||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
import SectionMain from '../components/SectionMain';
|
import SectionMain from '../components/SectionMain';
|
||||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
import { fetch } from '../stores/job_logs/job_logsSlice';
|
||||||
|
import ListJob_logs from '../components/Job_logs/ListJob_logs';
|
||||||
|
|
||||||
const MyLogsPage = () => {
|
const MyLogsPage = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||||
|
const { job_logs, loading } = useAppSelector((state) => state.job_logs);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser?.id) {
|
||||||
|
dispatch(fetch({ filter: { employee: currentUser.id }, page: currentPage }));
|
||||||
|
}
|
||||||
|
}, [dispatch, currentUser, currentPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@ -17,9 +31,14 @@ const MyLogsPage = () => {
|
|||||||
<SectionTitleLineWithButton icon={mdiViewList} title="My Logs" main>
|
<SectionTitleLineWithButton icon={mdiViewList} title="My Logs" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<ListJob_logs
|
||||||
<p>List of my job logs.</p>
|
job_logs={job_logs}
|
||||||
</CardBox>
|
loading={loading}
|
||||||
|
onDelete={() => console.log('Delete')}
|
||||||
|
currentPage={currentPage}
|
||||||
|
numPages={1}
|
||||||
|
onPageChange={setCurrentPage}
|
||||||
|
/>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
73
frontend/src/pages/reports.tsx
Normal file
73
frontend/src/pages/reports.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { mdiChartBar } from '@mdi/js';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import React, { ReactElement, useState, useEffect } from 'react';
|
||||||
|
import CardBox from '../components/CardBox';
|
||||||
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
import SectionMain from '../components/SectionMain';
|
||||||
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||||
|
import { getPageTitle } from '../config';
|
||||||
|
import FormField from '../components/FormField';
|
||||||
|
import BaseButton from '../components/BaseButton';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReportsPage = () => {
|
||||||
|
const [reportData, setReportData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [filters, setFilters] = useState({
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
employeeId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchReport = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/reports/payroll', filters);
|
||||||
|
setReportData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch report:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Payroll Reports')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton icon={mdiChartBar} title='Payroll Reports' main>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox className="mb-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
<FormField label="Start Date">
|
||||||
|
<input type="date" value={filters.startDate} onChange={e => setFilters({...filters, startDate: e.target.value})} className="px-3 py-2 border border-gray-300 rounded" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="End Date">
|
||||||
|
<input type="date" value={filters.endDate} onChange={e => setFilters({...filters, endDate: e.target.value})} className="px-3 py-2 border border-gray-300 rounded" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Employee ID (Optional)">
|
||||||
|
<input type="text" value={filters.employeeId} onChange={e => setFilters({...filters, employeeId: e.target.value})} className="px-3 py-2 border border-gray-300 rounded" />
|
||||||
|
</FormField>
|
||||||
|
<div className="flex items-end">
|
||||||
|
<BaseButton label="Generate Report" color="info" onClick={fetchReport} disabled={loading} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
{reportData && (
|
||||||
|
<CardBox>
|
||||||
|
<pre>{JSON.stringify(reportData, null, 2)}</pre>
|
||||||
|
</CardBox>
|
||||||
|
)}
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReportsPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportsPage;
|
||||||
Loading…
x
Reference in New Issue
Block a user