version 1.2
This commit is contained in:
parent
0b3a035811
commit
1387fa9cf0
File diff suppressed because one or more lines are too long
@ -19,6 +19,9 @@ module.exports = class BookingsDBApi {
|
||||
start_date: data.start_date || null,
|
||||
end_date: data.end_date || null,
|
||||
status: data.status || null,
|
||||
vehiclenumber: data.vehiclenumber || null,
|
||||
phone: data.phone || null,
|
||||
email: data.email || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -45,6 +48,9 @@ module.exports = class BookingsDBApi {
|
||||
start_date: item.start_date || null,
|
||||
end_date: item.end_date || null,
|
||||
status: item.status || null,
|
||||
vehiclenumber: item.vehiclenumber || null,
|
||||
phone: item.phone || null,
|
||||
email: item.email || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -78,6 +84,13 @@ module.exports = class BookingsDBApi {
|
||||
|
||||
if (data.status !== undefined) updatePayload.status = data.status;
|
||||
|
||||
if (data.vehiclenumber !== undefined)
|
||||
updatePayload.vehiclenumber = data.vehiclenumber;
|
||||
|
||||
if (data.phone !== undefined) updatePayload.phone = data.phone;
|
||||
|
||||
if (data.email !== undefined) updatePayload.email = data.email;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await bookings.update(updatePayload, { transaction });
|
||||
@ -213,6 +226,31 @@ module.exports = class BookingsDBApi {
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.vehiclenumber) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike(
|
||||
'bookings',
|
||||
'vehiclenumber',
|
||||
filter.vehiclenumber,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.phone) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('bookings', 'phone', filter.phone),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.email) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('bookings', 'email', filter.email),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.calendarStart && filter.calendarEnd) {
|
||||
where = {
|
||||
...where,
|
||||
|
||||
49
backend/src/db/migrations/1755286505240.js
Normal file
49
backend/src/db/migrations/1755286505240.js
Normal file
@ -0,0 +1,49 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.addColumn(
|
||||
'bookings',
|
||||
'vehiclenumber',
|
||||
{
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.removeColumn('bookings', 'vehiclenumber', {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
47
backend/src/db/migrations/1755286528532.js
Normal file
47
backend/src/db/migrations/1755286528532.js
Normal file
@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.addColumn(
|
||||
'bookings',
|
||||
'phone',
|
||||
{
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.removeColumn('bookings', 'phone', { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
47
backend/src/db/migrations/1755286555500.js
Normal file
47
backend/src/db/migrations/1755286555500.js
Normal file
@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.addColumn(
|
||||
'bookings',
|
||||
'email',
|
||||
{
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.removeColumn('bookings', 'email', { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -32,6 +32,18 @@ module.exports = function (sequelize, DataTypes) {
|
||||
values: ['Pending', 'Inspected', 'Cancelled'],
|
||||
},
|
||||
|
||||
vehiclenumber: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
phone: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
email: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
|
||||
@ -22,6 +22,12 @@ const BookingsData = [
|
||||
// type code here for "relation_one" field
|
||||
|
||||
status: 'Inspected',
|
||||
|
||||
vehiclenumber: 'Emil Kraepelin',
|
||||
|
||||
phone: 'Lynn Margulis',
|
||||
|
||||
email: 'Sigmund Freud',
|
||||
},
|
||||
|
||||
{
|
||||
@ -33,7 +39,13 @@ const BookingsData = [
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
status: 'Inspected',
|
||||
status: 'Pending',
|
||||
|
||||
vehiclenumber: 'Max Planck',
|
||||
|
||||
phone: 'Tycho Brahe',
|
||||
|
||||
email: 'Francis Galton',
|
||||
},
|
||||
|
||||
{
|
||||
@ -45,7 +57,13 @@ const BookingsData = [
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
status: 'Inspected',
|
||||
status: 'Cancelled',
|
||||
|
||||
vehiclenumber: 'Emil Fischer',
|
||||
|
||||
phone: 'Francis Crick',
|
||||
|
||||
email: 'Jean Baptiste Lamarck',
|
||||
},
|
||||
|
||||
{
|
||||
@ -57,19 +75,13 @@ const BookingsData = [
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
status: 'Cancelled',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Vehicle Inspection - William',
|
||||
|
||||
start_date: new Date('2023-11-05T11:00:00Z'),
|
||||
|
||||
end_date: new Date('2023-11-05T12:00:00Z'),
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
status: 'Inspected',
|
||||
|
||||
vehiclenumber: 'Nicolaus Copernicus',
|
||||
|
||||
phone: 'Charles Sherrington',
|
||||
|
||||
email: 'Ernest Rutherford',
|
||||
},
|
||||
];
|
||||
|
||||
@ -89,10 +101,6 @@ const CategoriesData = [
|
||||
{
|
||||
name: 'Startup',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Non-Profit',
|
||||
},
|
||||
];
|
||||
|
||||
const ContactsData = [
|
||||
@ -135,16 +143,6 @@ const ContactsData = [
|
||||
|
||||
// type code here for "relation_one" field
|
||||
},
|
||||
|
||||
{
|
||||
first_name: 'Eve',
|
||||
|
||||
last_name: 'Adams',
|
||||
|
||||
email: 'eve.adams@example.com',
|
||||
|
||||
// type code here for "relation_one" field
|
||||
},
|
||||
];
|
||||
|
||||
const DepartmentsData = [
|
||||
@ -171,19 +169,13 @@ const DepartmentsData = [
|
||||
|
||||
// type code here for "relation_many" field
|
||||
},
|
||||
|
||||
{
|
||||
name: 'IT',
|
||||
|
||||
// type code here for "relation_many" field
|
||||
},
|
||||
];
|
||||
|
||||
const LeadsData = [
|
||||
{
|
||||
name: 'Acme Corp',
|
||||
|
||||
status: 'Qualified',
|
||||
status: 'Contacted',
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
@ -193,7 +185,7 @@ const LeadsData = [
|
||||
{
|
||||
name: 'Beta LLC',
|
||||
|
||||
status: 'Lost',
|
||||
status: 'Contacted',
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
@ -203,7 +195,7 @@ const LeadsData = [
|
||||
{
|
||||
name: 'Gamma Inc',
|
||||
|
||||
status: 'Qualified',
|
||||
status: 'Contacted',
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
@ -219,16 +211,6 @@ const LeadsData = [
|
||||
|
||||
// type code here for "relation_one" field
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Epsilon Co',
|
||||
|
||||
status: 'Contacted',
|
||||
|
||||
// type code here for "relation_one" field
|
||||
|
||||
// type code here for "relation_one" field
|
||||
},
|
||||
];
|
||||
|
||||
// Similar logic for "relation_many"
|
||||
@ -277,17 +259,6 @@ async function associateBookingWithUser() {
|
||||
if (Booking3?.setUser) {
|
||||
await Booking3.setUser(relatedUser3);
|
||||
}
|
||||
|
||||
const relatedUser4 = await Users.findOne({
|
||||
offset: Math.floor(Math.random() * (await Users.count())),
|
||||
});
|
||||
const Booking4 = await Bookings.findOne({
|
||||
order: [['id', 'ASC']],
|
||||
offset: 4,
|
||||
});
|
||||
if (Booking4?.setUser) {
|
||||
await Booking4.setUser(relatedUser4);
|
||||
}
|
||||
}
|
||||
|
||||
async function associateContactWithOwner() {
|
||||
@ -334,17 +305,6 @@ async function associateContactWithOwner() {
|
||||
if (Contact3?.setOwner) {
|
||||
await Contact3.setOwner(relatedOwner3);
|
||||
}
|
||||
|
||||
const relatedOwner4 = await Users.findOne({
|
||||
offset: Math.floor(Math.random() * (await Users.count())),
|
||||
});
|
||||
const Contact4 = await Contacts.findOne({
|
||||
order: [['id', 'ASC']],
|
||||
offset: 4,
|
||||
});
|
||||
if (Contact4?.setOwner) {
|
||||
await Contact4.setOwner(relatedOwner4);
|
||||
}
|
||||
}
|
||||
|
||||
// Similar logic for "relation_many"
|
||||
@ -393,17 +353,6 @@ async function associateLeadWithOwner() {
|
||||
if (Lead3?.setOwner) {
|
||||
await Lead3.setOwner(relatedOwner3);
|
||||
}
|
||||
|
||||
const relatedOwner4 = await Users.findOne({
|
||||
offset: Math.floor(Math.random() * (await Users.count())),
|
||||
});
|
||||
const Lead4 = await Leads.findOne({
|
||||
order: [['id', 'ASC']],
|
||||
offset: 4,
|
||||
});
|
||||
if (Lead4?.setOwner) {
|
||||
await Lead4.setOwner(relatedOwner4);
|
||||
}
|
||||
}
|
||||
|
||||
async function associateLeadWithCategory() {
|
||||
@ -450,17 +399,6 @@ async function associateLeadWithCategory() {
|
||||
if (Lead3?.setCategory) {
|
||||
await Lead3.setCategory(relatedCategory3);
|
||||
}
|
||||
|
||||
const relatedCategory4 = await Categories.findOne({
|
||||
offset: Math.floor(Math.random() * (await Categories.count())),
|
||||
});
|
||||
const Lead4 = await Leads.findOne({
|
||||
order: [['id', 'ASC']],
|
||||
offset: 4,
|
||||
});
|
||||
if (Lead4?.setCategory) {
|
||||
await Lead4.setCategory(relatedCategory4);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
12
backend/src/holidays.js
Normal file
12
backend/src/holidays.js
Normal file
@ -0,0 +1,12 @@
|
||||
// List of Fiji public holidays (YYYY-MM-DD)
|
||||
module.exports = [
|
||||
'2024-01-01', // New Year's Day
|
||||
'2024-01-02', // Day after New Year's Day
|
||||
'2024-04-14', // Good Friday
|
||||
'2024-04-17', // Easter Monday
|
||||
'2024-05-01', // Labour Day
|
||||
'2024-10-10', // Fiji Day
|
||||
'2024-12-25', // Christmas Day
|
||||
'2024-12-26', // Boxing Day
|
||||
// Add more dates as needed
|
||||
];
|
||||
@ -8,6 +8,9 @@ const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
const moment = require('moment');
|
||||
const holidays = require('../holidays');
|
||||
const ValidationError = require('../services/notifications/errors/validation');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('bookings'));
|
||||
@ -23,6 +26,15 @@ router.use(checkCrudPermissions('bookings'));
|
||||
* title:
|
||||
* type: string
|
||||
* default: title
|
||||
* vehiclenumber:
|
||||
* type: string
|
||||
* default: vehiclenumber
|
||||
* phone:
|
||||
* type: string
|
||||
* default: phone
|
||||
* email:
|
||||
* type: string
|
||||
* default: email
|
||||
|
||||
*
|
||||
*/
|
||||
@ -73,6 +85,28 @@ router.post(
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
// Booking validations
|
||||
const { start_date, vehiclenumber, phone, email } = req.body.data;
|
||||
if (!vehiclenumber) {
|
||||
throw new ValidationError('vehicleNumberRequired', 'Vehicle number is required.');
|
||||
}
|
||||
if (!phone && !email) {
|
||||
throw new ValidationError('contactInfoRequired', 'Either phone or email must be provided.');
|
||||
}
|
||||
const date = moment(start_date);
|
||||
const day = date.day(); // Sunday = 0, Saturday = 6
|
||||
const dateStr = date.format('YYYY-MM-DD');
|
||||
if (day === 0) {
|
||||
throw new ValidationError('noSundayBookings', 'Bookings cannot be made on Sundays.');
|
||||
}
|
||||
if (holidays.includes(dateStr)) {
|
||||
throw new ValidationError('noHolidayBookings', 'Bookings cannot be made on public holidays.');
|
||||
}
|
||||
if (day === 6 && date.hour() > 13) {
|
||||
throw new ValidationError('saturdayCutoff', 'Saturday bookings are only allowed until 1 PM.');
|
||||
}
|
||||
|
||||
|
||||
const link = new URL(referer);
|
||||
await BookingsService.create(
|
||||
req.body.data,
|
||||
@ -305,7 +339,16 @@ router.get(
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await BookingsDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'title', 'start_date', 'end_date'];
|
||||
const fields = [
|
||||
'id',
|
||||
'title',
|
||||
'vehiclenumber',
|
||||
'phone',
|
||||
'email',
|
||||
|
||||
'start_date',
|
||||
'end_date',
|
||||
];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
|
||||
@ -43,7 +43,7 @@ module.exports = class SearchService {
|
||||
const tableColumns = {
|
||||
users: ['firstName', 'lastName', 'phoneNumber', 'email'],
|
||||
|
||||
bookings: ['title'],
|
||||
bookings: ['title', 'vehiclenumber', 'phone', 'email'],
|
||||
|
||||
categories: ['name'],
|
||||
|
||||
|
||||
@ -124,6 +124,31 @@ const CardBookings = ({
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Vehiclenumber
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.vehiclenumber}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>Phone</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>{item.phone}</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>Email</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>{item.email}</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@ -81,6 +81,23 @@ const ListBookings = ({
|
||||
<p className={'text-xs text-gray-500 '}>Status</p>
|
||||
<p className={'line-clamp-2'}>{item.status}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>
|
||||
Vehiclenumber
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>{item.vehiclenumber}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Phone</p>
|
||||
<p className={'line-clamp-2'}>{item.phone}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Email</p>
|
||||
<p className={'line-clamp-2'}>{item.email}</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
|
||||
@ -114,6 +114,42 @@ export const loadColumns = async (
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'vehiclenumber',
|
||||
headerName: 'Vehiclenumber',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'phone',
|
||||
headerName: 'Phone',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'email',
|
||||
headerName: 'Email',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'actions',
|
||||
type: 'actions',
|
||||
|
||||
@ -20,3 +20,15 @@ export const getPageTitle = (currentPageTitle: string) =>
|
||||
`${currentPageTitle} — ${appTitle}`;
|
||||
|
||||
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || '';
|
||||
|
||||
// Fiji public holidays (YYYY-MM-DD)
|
||||
export const holidays: string[] = [
|
||||
'2024-01-01', // New Year's Day
|
||||
'2024-01-02', // Day after New Year's Day
|
||||
'2024-04-14', // Good Friday
|
||||
'2024-04-17', // Easter Monday
|
||||
'2024-05-01', // Labour Day
|
||||
'2024-10-10', // Fiji Day
|
||||
'2024-12-25', // Christmas Day
|
||||
'2024-12-26', // Boxing Day
|
||||
];
|
||||
|
||||
@ -45,6 +45,12 @@ const EditBookings = () => {
|
||||
user: null,
|
||||
|
||||
status: '',
|
||||
|
||||
vehiclenumber: '',
|
||||
|
||||
phone: '',
|
||||
|
||||
email: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
@ -160,6 +166,18 @@ const EditBookings = () => {
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Vehiclenumber'>
|
||||
<Field name='vehiclenumber' placeholder='Vehiclenumber' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Phone'>
|
||||
<Field name='phone' placeholder='Phone' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Email'>
|
||||
<Field name='email' placeholder='Email' />
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
|
||||
@ -45,6 +45,12 @@ const EditBookingsPage = () => {
|
||||
user: null,
|
||||
|
||||
status: '',
|
||||
|
||||
vehiclenumber: '',
|
||||
|
||||
phone: '',
|
||||
|
||||
email: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
@ -158,6 +164,18 @@ const EditBookingsPage = () => {
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Vehiclenumber'>
|
||||
<Field name='vehiclenumber' placeholder='Vehiclenumber' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Phone'>
|
||||
<Field name='phone' placeholder='Phone' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Email'>
|
||||
<Field name='email' placeholder='Email' />
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
|
||||
@ -30,6 +30,9 @@ const BookingsTablesPage = () => {
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Title', title: 'title' },
|
||||
{ label: 'Vehiclenumber', title: 'vehiclenumber' },
|
||||
{ label: 'Phone', title: 'phone' },
|
||||
{ label: 'Email', title: 'email' },
|
||||
|
||||
{ label: 'StartDate', title: 'start_date', date: 'true' },
|
||||
{ label: 'EndDate', title: 'end_date', date: 'true' },
|
||||
|
||||
@ -4,9 +4,11 @@ import {
|
||||
mdiMail,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import { holidays } from '../../config';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutGuest from '../../layouts/Guest';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
@ -33,15 +35,11 @@ import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
title: '',
|
||||
|
||||
start_date: '',
|
||||
|
||||
end_date: '',
|
||||
|
||||
user: '',
|
||||
|
||||
status: 'Pending',
|
||||
date: '',
|
||||
timeSlot: '',
|
||||
vehiclenumber: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
};
|
||||
|
||||
const BookingsNew = () => {
|
||||
@ -69,73 +67,65 @@ const BookingsNew = () => {
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
initialValues={
|
||||
dateRangeStart && dateRangeEnd
|
||||
? {
|
||||
...initialValues,
|
||||
start_date:
|
||||
moment(dateRangeStart).format('YYYY-MM-DDTHH:mm'),
|
||||
end_date: moment(dateRangeEnd).format('YYYY-MM-DDTHH:mm'),
|
||||
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
||||
{({ values }) => {
|
||||
// Generate 20-minute slots between 08:00 and 16:00
|
||||
const slots: string[] = [];
|
||||
if (values.date) {
|
||||
const date = moment(values.date);
|
||||
const day = date.day(); // Sunday = 0, Saturday = 6
|
||||
const dateStr = date.format('YYYY-MM-DD');
|
||||
const startHour = 8;
|
||||
const endHour = 16;
|
||||
let cursor = date.clone().hour(startHour).minute(0);
|
||||
while (cursor.hour() < endHour) {
|
||||
const slot = cursor.format('HH:mm');
|
||||
// Exclude Sundays and holidays
|
||||
if (day !== 0 && !holidays.includes(dateStr)) {
|
||||
// Saturday cutoff at 13:00
|
||||
if (!(day === 6 && cursor.hour() > 13)) {
|
||||
slots.push(slot);
|
||||
}
|
||||
}
|
||||
: initialValues
|
||||
}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Title'>
|
||||
<Field name='title' placeholder='Title' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='StartDate'>
|
||||
<Field
|
||||
type='datetime-local'
|
||||
name='start_date'
|
||||
placeholder='StartDate'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='EndDate'>
|
||||
<Field
|
||||
type='datetime-local'
|
||||
name='end_date'
|
||||
placeholder='EndDate'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'users'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Status' labelFor='status'>
|
||||
<Field name='status' id='status' component='select'>
|
||||
<option value='Pending'>Pending</option>
|
||||
|
||||
<option value='Inspected'>Inspected</option>
|
||||
|
||||
<option value='Cancelled'>Cancelled</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/bookings/bookings-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
cursor = cursor.add(20, 'minutes');
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Form>
|
||||
<FormField label="Date">
|
||||
<Field type="date" name="date" />
|
||||
</FormField>
|
||||
<FormField label="Time Slot">
|
||||
<Field
|
||||
component={SelectField}
|
||||
name="timeSlot"
|
||||
options={slots.map((s) => ({ label: s, value: s }))}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Vehicle Number">
|
||||
<Field name="vehiclenumber" placeholder="Vehicle Number" />
|
||||
</FormField>
|
||||
<FormField label="Phone">
|
||||
<Field name="phone" placeholder="Your phone" />
|
||||
</FormField>
|
||||
<FormField label="Email">
|
||||
<Field name="email" placeholder="Your email" />
|
||||
</FormField>
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" />
|
||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
||||
<BaseButton
|
||||
type="button"
|
||||
color="danger"
|
||||
outline
|
||||
label="Cancel"
|
||||
onClick={() => router.push('/')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
@ -145,9 +135,9 @@ const BookingsNew = () => {
|
||||
|
||||
BookingsNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_BOOKINGS'}>
|
||||
<LayoutGuest>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
</LayoutGuest>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -30,6 +30,9 @@ const BookingsTablesPage = () => {
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Title', title: 'title' },
|
||||
{ label: 'Vehiclenumber', title: 'vehiclenumber' },
|
||||
{ label: 'Phone', title: 'phone' },
|
||||
{ label: 'Email', title: 'email' },
|
||||
|
||||
{ label: 'StartDate', title: 'start_date', date: 'true' },
|
||||
{ label: 'EndDate', title: 'end_date', date: 'true' },
|
||||
|
||||
@ -108,6 +108,21 @@ const BookingsView = () => {
|
||||
<p>{bookings?.status ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Vehiclenumber</p>
|
||||
<p>{bookings?.vehiclenumber}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Phone</p>
|
||||
<p>{bookings?.phone}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Email</p>
|
||||
<p>{bookings?.email}</p>
|
||||
</div>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
|
||||
@ -96,7 +96,7 @@ export default function WebSite() {
|
||||
<FeaturesSection
|
||||
projectName={'Vehicle Inspection Booking System'}
|
||||
image={['Dashboard showcasing CRM features']}
|
||||
withBg={1}
|
||||
withBg={0}
|
||||
features={features_points}
|
||||
mainText={`Discover Key Features of ${projectName}`}
|
||||
subTitle={`Unlock the full potential of your legal operations with ${projectName}. Streamline processes, enhance collaboration, and boost productivity.`}
|
||||
|
||||
@ -155,6 +155,12 @@ const UsersView = () => {
|
||||
<th>EndDate</th>
|
||||
|
||||
<th>Status</th>
|
||||
|
||||
<th>Vehiclenumber</th>
|
||||
|
||||
<th>Phone</th>
|
||||
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -180,6 +186,14 @@ const UsersView = () => {
|
||||
</td>
|
||||
|
||||
<td data-label='status'>{item.status}</td>
|
||||
|
||||
<td data-label='vehiclenumber'>
|
||||
{item.vehiclenumber}
|
||||
</td>
|
||||
|
||||
<td data-label='phone'>{item.phone}</td>
|
||||
|
||||
<td data-label='email'>{item.email}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@ -110,7 +110,7 @@ export default function WebSite() {
|
||||
<FeaturesSection
|
||||
projectName={'Vehicle Inspection Booking System'}
|
||||
image={['Icons representing CRM features']}
|
||||
withBg={1}
|
||||
withBg={0}
|
||||
features={features_points}
|
||||
mainText={`Unleash the Power of ${projectName}`}
|
||||
subTitle={`Explore the robust features of ${projectName} designed to elevate your legal practice. Enhance productivity, streamline operations, and drive success.`}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user