Forced merge: merge ai-dev into master
This commit is contained in:
commit
0a30b833e2
File diff suppressed because one or more lines are too long
@ -152,6 +152,14 @@ module.exports = class EmployeesDBApi {
|
||||
|
||||
const output = employees.get({ plain: true });
|
||||
|
||||
output.fuelcard_driver = await employees.getFuelcard_driver({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.fuellog_driver = await employees.getFuellog_driver({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.organizations = await employees.getOrganizations({
|
||||
transaction,
|
||||
});
|
||||
|
||||
421
backend/src/db/api/fuelcard.js
Normal file
421
backend/src/db/api/fuelcard.js
Normal file
@ -0,0 +1,421 @@
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class FuelcardDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const fuelcard = await db.fuelcard.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
card_number: data.card_number || null,
|
||||
limit: data.limit || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await fuelcard.setOrganizations(data.organizations || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await fuelcard.setVehicle(data.vehicle || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await fuelcard.setDriver(data.driver || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
// Prepare data - wrapping individual data transformations in a map() method
|
||||
const fuelcardData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
card_number: item.card_number || null,
|
||||
limit: item.limit || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const fuelcard = await db.fuelcard.bulkCreate(fuelcardData, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const globalAccess = currentUser.app_role?.globalAccess;
|
||||
|
||||
const fuelcard = await db.fuelcard.findByPk(id, {}, { transaction });
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
if (data.card_number !== undefined)
|
||||
updatePayload.card_number = data.card_number;
|
||||
|
||||
if (data.limit !== undefined) updatePayload.limit = data.limit;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await fuelcard.update(updatePayload, { transaction });
|
||||
|
||||
if (data.organizations !== undefined) {
|
||||
await fuelcard.setOrganizations(
|
||||
data.organizations,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.vehicle !== undefined) {
|
||||
await fuelcard.setVehicle(
|
||||
data.vehicle,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.driver !== undefined) {
|
||||
await fuelcard.setDriver(
|
||||
data.driver,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const fuelcard = await db.fuelcard.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of fuelcard) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of fuelcard) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const fuelcard = await db.fuelcard.findByPk(id, options);
|
||||
|
||||
await fuelcard.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await fuelcard.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const fuelcard = await db.fuelcard.findOne({ where }, { transaction });
|
||||
|
||||
if (!fuelcard) {
|
||||
return fuelcard;
|
||||
}
|
||||
|
||||
const output = fuelcard.get({ plain: true });
|
||||
|
||||
output.fuellog_fuel_card = await fuelcard.getFuellog_fuel_card({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.organizations = await fuelcard.getOrganizations({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.vehicle = await fuelcard.getVehicle({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.driver = await fuelcard.getDriver({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async findAll(filter, globalAccess, options) {
|
||||
const limit = filter.limit || 0;
|
||||
let offset = 0;
|
||||
let where = {};
|
||||
const currentPage = +filter.page;
|
||||
|
||||
const user = (options && options.currentUser) || null;
|
||||
const userOrganizations = (user && user.organizations?.id) || null;
|
||||
|
||||
if (userOrganizations) {
|
||||
if (options?.currentUser?.organizationsId) {
|
||||
where.organizationsId = options.currentUser.organizationsId;
|
||||
}
|
||||
}
|
||||
|
||||
offset = currentPage * limit;
|
||||
|
||||
const orderBy = null;
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
let include = [
|
||||
{
|
||||
model: db.organizations,
|
||||
as: 'organizations',
|
||||
},
|
||||
|
||||
{
|
||||
model: db.vehicle,
|
||||
as: 'vehicle',
|
||||
|
||||
where: filter.vehicle
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.vehicle
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
license_plate: {
|
||||
[Op.or]: filter.vehicle
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
|
||||
{
|
||||
model: db.employees,
|
||||
as: 'driver',
|
||||
|
||||
where: filter.driver
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.driver
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
first_name: {
|
||||
[Op.or]: filter.driver
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
where = {
|
||||
...where,
|
||||
['id']: Utils.uuid(filter.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.card_number) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('fuelcard', 'card_number', filter.card_number),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.limitRange) {
|
||||
const [start, end] = filter.limitRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
limit: {
|
||||
...where.limit,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
limit: {
|
||||
...where.limit,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
active: filter.active === true || filter.active === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.organizations) {
|
||||
const listItems = filter.organizations.split('|').map((item) => {
|
||||
return Utils.uuid(item);
|
||||
});
|
||||
|
||||
where = {
|
||||
...where,
|
||||
organizationsId: { [Op.or]: listItems },
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.createdAtRange) {
|
||||
const [start, end] = filter.createdAtRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
['createdAt']: {
|
||||
...where.createdAt,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
['createdAt']: {
|
||||
...where.createdAt,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (globalAccess) {
|
||||
delete where.organizationsId;
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options?.transaction,
|
||||
logging: console.log,
|
||||
};
|
||||
|
||||
if (!options?.countOnly) {
|
||||
queryOptions.limit = limit ? Number(limit) : undefined;
|
||||
queryOptions.offset = offset ? Number(offset) : undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows, count } = await db.fuelcard.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
count: count,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error executing query:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async findAllAutocomplete(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
globalAccess,
|
||||
organizationId,
|
||||
) {
|
||||
let where = {};
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ ['id']: Utils.uuid(query) },
|
||||
Utils.ilike('fuelcard', 'card_number', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.fuelcard.findAll({
|
||||
attributes: ['id', 'card_number'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['card_number', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.card_number,
|
||||
}));
|
||||
}
|
||||
};
|
||||
54
backend/src/db/migrations/1752313601225.js
Normal file
54
backend/src/db/migrations/1752313601225.js
Normal file
@ -0,0 +1,54 @@
|
||||
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(
|
||||
'fuelcard',
|
||||
'driverId',
|
||||
{
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
|
||||
references: {
|
||||
model: 'employees',
|
||||
key: 'id',
|
||||
},
|
||||
},
|
||||
{ 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('fuelcard', 'driverId', {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -46,6 +46,22 @@ module.exports = function (sequelize, DataTypes) {
|
||||
employees.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.employees.hasMany(db.fuelcard, {
|
||||
as: 'fuelcard_driver',
|
||||
foreignKey: {
|
||||
name: 'driverId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.employees.hasMany(db.fuellog, {
|
||||
as: 'fuellog_driver',
|
||||
foreignKey: {
|
||||
name: 'driverId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.employees.belongsTo(db.organizations, {
|
||||
|
||||
85
backend/src/db/models/fuelcard.js
Normal file
85
backend/src/db/models/fuelcard.js
Normal file
@ -0,0 +1,85 @@
|
||||
const config = require('../../config');
|
||||
const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const fuelcard = sequelize.define(
|
||||
'fuelcard',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
card_number: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
limit: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
fuelcard.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.fuelcard.hasMany(db.fuellog, {
|
||||
as: 'fuellog_fuel_card',
|
||||
foreignKey: {
|
||||
name: 'fuel_cardId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.fuelcard.belongsTo(db.organizations, {
|
||||
as: 'organizations',
|
||||
foreignKey: {
|
||||
name: 'organizationsId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.fuelcard.belongsTo(db.vehicle, {
|
||||
as: 'vehicle',
|
||||
foreignKey: {
|
||||
name: 'vehicleId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.fuelcard.belongsTo(db.employees, {
|
||||
as: 'driver',
|
||||
foreignKey: {
|
||||
name: 'driverId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.fuelcard.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.fuelcard.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return fuelcard;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
138
frontend/src/components/Fuelcard/CardFuelcard.tsx
Normal file
138
frontend/src/components/Fuelcard/CardFuelcard.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
import React from 'react';
|
||||
import ImageField from '../ImageField';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { Pagination } from '../Pagination';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
fuelcard: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardFuelcard = ({
|
||||
fuelcard,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const asideScrollbarsStyle = useAppSelector(
|
||||
(state) => state.style.asideScrollbarsStyle,
|
||||
);
|
||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
||||
const darkMode = useAppSelector((state) => state.style.darkMode);
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_FUELCARD');
|
||||
|
||||
return (
|
||||
<div className={'p-4'}>
|
||||
{loading && <LoadingSpinner />}
|
||||
<ul
|
||||
role='list'
|
||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||
>
|
||||
{!loading &&
|
||||
fuelcard.map((item, index) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className={`overflow-hidden ${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
||||
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}
|
||||
>
|
||||
<Link
|
||||
href={`/fuelcard/fuelcard-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.card_number}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/fuelcard/fuelcard-edit/?id=${item.id}`}
|
||||
pathView={`/fuelcard/fuelcard-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Card number
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.card_number}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>Limit</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>{item.limit}</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Vehicle
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.vehicleOneListFormatter(item.vehicle)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Driver
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.employeesOneListFormatter(item.driver)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && fuelcard.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
<div className={'flex items-center justify-center my-6'}>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
numPages={numPages}
|
||||
setCurrentPage={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardFuelcard;
|
||||
106
frontend/src/components/Fuelcard/ListFuelcard.tsx
Normal file
106
frontend/src/components/Fuelcard/ListFuelcard.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import CardBox from '../CardBox';
|
||||
import ImageField from '../ImageField';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import { Pagination } from '../Pagination';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
fuelcard: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListFuelcard = ({
|
||||
fuelcard,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_FUELCARD');
|
||||
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||
{loading && <LoadingSpinner />}
|
||||
{!loading &&
|
||||
fuelcard.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/fuelcard/fuelcard-view/?id=${item.id}`}
|
||||
className={
|
||||
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
|
||||
}
|
||||
>
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Card number</p>
|
||||
<p className={'line-clamp-2'}>{item.card_number}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Limit</p>
|
||||
<p className={'line-clamp-2'}>{item.limit}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Vehicle</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.vehicleOneListFormatter(item.vehicle)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Driver</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.employeesOneListFormatter(item.driver)}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/fuelcard/fuelcard-edit/?id=${item.id}`}
|
||||
pathView={`/fuelcard/fuelcard-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && fuelcard.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={'flex items-center justify-center my-6'}>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
numPages={numPages}
|
||||
setCurrentPage={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListFuelcard;
|
||||
128
frontend/src/components/Fuelcard/configureFuelcardCols.tsx
Normal file
128
frontend/src/components/Fuelcard/configureFuelcardCols.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_FUELCARD');
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'card_number',
|
||||
headerName: 'Card number',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'limit',
|
||||
headerName: 'Limit',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
type: 'number',
|
||||
},
|
||||
|
||||
{
|
||||
field: 'vehicle',
|
||||
headerName: 'Vehicle',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
getOptionValue: (value: any) => value?.id,
|
||||
getOptionLabel: (value: any) => value?.label,
|
||||
valueOptions: await callOptionsApi('vehicle'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'driver',
|
||||
headerName: 'Driver',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
getOptionValue: (value: any) => value?.id,
|
||||
getOptionLabel: (value: any) => value?.label,
|
||||
valueOptions: await callOptionsApi('employees'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'actions',
|
||||
type: 'actions',
|
||||
minWidth: 30,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
getActions: (params: GridRowParams) => {
|
||||
return [
|
||||
<div key={params?.row?.id}>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={params?.row?.id}
|
||||
pathEdit={`/fuelcard/fuelcard-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/fuelcard/fuelcard-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -17,9 +17,9 @@ export default function WebSiteFooter({ projectName }: WebSiteFooterProps) {
|
||||
const borders = useAppSelector((state) => state.style.borders);
|
||||
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
|
||||
|
||||
const style = FooterStyle.WITH_PROJECT_NAME;
|
||||
const style = FooterStyle.WITH_PAGES;
|
||||
|
||||
const design = FooterDesigns.DEFAULT_DESIGN;
|
||||
const design = FooterDesigns.DESIGN_DIVERSITY;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -19,7 +19,7 @@ export default function WebSiteHeader({ projectName }: WebSiteHeaderProps) {
|
||||
|
||||
const style = HeaderStyle.PAGES_RIGHT;
|
||||
|
||||
const design = HeaderDesigns.DESIGN_DIVERSITY;
|
||||
const design = HeaderDesigns.DEFAULT_DESIGN;
|
||||
return (
|
||||
<header id='websiteHeader' className='overflow-hidden'>
|
||||
<div
|
||||
|
||||
@ -84,6 +84,100 @@ const EmployeesView = () => {
|
||||
<p>{employees?.organizations?.name ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Fuelcard Driver</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Card number</th>
|
||||
|
||||
<th>Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{employees.fuelcard_driver &&
|
||||
Array.isArray(employees.fuelcard_driver) &&
|
||||
employees.fuelcard_driver.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/fuelcard/fuelcard-view/?id=${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<td data-label='card_number'>{item.card_number}</td>
|
||||
|
||||
<td data-label='limit'>{item.limit}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!employees?.fuelcard_driver?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Fuellog Driver</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
|
||||
<th>Quantity</th>
|
||||
|
||||
<th>Unit cost</th>
|
||||
|
||||
<th>Total cost</th>
|
||||
|
||||
<th>Odometer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{employees.fuellog_driver &&
|
||||
Array.isArray(employees.fuellog_driver) &&
|
||||
employees.fuellog_driver.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/fuellog/fuellog-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='date'>
|
||||
{dataFormatter.dateTimeFormatter(item.date)}
|
||||
</td>
|
||||
|
||||
<td data-label='quantity'>{item.quantity}</td>
|
||||
|
||||
<td data-label='unit_cost'>{item.unit_cost}</td>
|
||||
|
||||
<td data-label='total_cost'>{item.total_cost}</td>
|
||||
|
||||
<td data-label='odometer'>{item.odometer}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!employees?.fuellog_driver?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
|
||||
173
frontend/src/pages/fuelcard/[fuelcardId].tsx
Normal file
173
frontend/src/pages/fuelcard/[fuelcardId].tsx
Normal file
@ -0,0 +1,173 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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 { 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 FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/fuelcard/fuelcardSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const EditFuelcard = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
organizations: null,
|
||||
|
||||
card_number: '',
|
||||
|
||||
limit: '',
|
||||
|
||||
vehicle: null,
|
||||
|
||||
driver: null,
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { fuelcard } = useAppSelector((state) => state.fuelcard);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const { fuelcardId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: fuelcardId }));
|
||||
}, [fuelcardId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof fuelcard === 'object') {
|
||||
setInitialValues(fuelcard);
|
||||
}
|
||||
}, [fuelcard]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof fuelcard === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = fuelcard[el]));
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [fuelcard]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: fuelcardId, data }));
|
||||
await router.push('/fuelcard/fuelcard-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit fuelcard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit fuelcard'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='organizations' labelFor='organizations'>
|
||||
<Field
|
||||
name='organizations'
|
||||
id='organizations'
|
||||
component={SelectField}
|
||||
options={initialValues.organizations}
|
||||
itemRef={'organizations'}
|
||||
showField={'name'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Card number'>
|
||||
<Field name='card_number' placeholder='Card number' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Limit'>
|
||||
<Field type='number' name='limit' placeholder='Limit' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Vehicle' labelFor='vehicle'>
|
||||
<Field
|
||||
name='vehicle'
|
||||
id='vehicle'
|
||||
component={SelectField}
|
||||
options={initialValues.vehicle}
|
||||
itemRef={'vehicle'}
|
||||
showField={'license_plate'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Driver' labelFor='driver'>
|
||||
<Field
|
||||
name='driver'
|
||||
id='driver'
|
||||
component={SelectField}
|
||||
options={initialValues.driver}
|
||||
itemRef={'employees'}
|
||||
showField={'first_name'}
|
||||
></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('/fuelcard/fuelcard-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditFuelcard.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditFuelcard;
|
||||
171
frontend/src/pages/fuelcard/fuelcard-edit.tsx
Normal file
171
frontend/src/pages/fuelcard/fuelcard-edit.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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 { 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 FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/fuelcard/fuelcardSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const EditFuelcardPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
organizations: null,
|
||||
|
||||
card_number: '',
|
||||
|
||||
limit: '',
|
||||
|
||||
vehicle: null,
|
||||
|
||||
driver: null,
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { fuelcard } = useAppSelector((state) => state.fuelcard);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: id }));
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof fuelcard === 'object') {
|
||||
setInitialValues(fuelcard);
|
||||
}
|
||||
}, [fuelcard]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof fuelcard === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = fuelcard[el]));
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [fuelcard]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/fuelcard/fuelcard-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit fuelcard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit fuelcard'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='organizations' labelFor='organizations'>
|
||||
<Field
|
||||
name='organizations'
|
||||
id='organizations'
|
||||
component={SelectField}
|
||||
options={initialValues.organizations}
|
||||
itemRef={'organizations'}
|
||||
showField={'name'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Card number'>
|
||||
<Field name='card_number' placeholder='Card number' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Limit'>
|
||||
<Field type='number' name='limit' placeholder='Limit' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Vehicle' labelFor='vehicle'>
|
||||
<Field
|
||||
name='vehicle'
|
||||
id='vehicle'
|
||||
component={SelectField}
|
||||
options={initialValues.vehicle}
|
||||
itemRef={'vehicle'}
|
||||
showField={'license_plate'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Driver' labelFor='driver'>
|
||||
<Field
|
||||
name='driver'
|
||||
id='driver'
|
||||
component={SelectField}
|
||||
options={initialValues.driver}
|
||||
itemRef={'employees'}
|
||||
showField={'first_name'}
|
||||
></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('/fuelcard/fuelcard-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditFuelcardPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditFuelcardPage;
|
||||
169
frontend/src/pages/fuelcard/fuelcard-list.tsx
Normal file
169
frontend/src/pages/fuelcard/fuelcard-list.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } 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 TableFuelcard from '../../components/Fuelcard/TableFuelcard';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/fuelcard/fuelcardSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const FuelcardTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Card number', title: 'card_number' },
|
||||
{ label: 'Limit', title: 'limit', number: 'true' },
|
||||
|
||||
{ label: 'Vehicle', title: 'vehicle' },
|
||||
|
||||
{ label: 'Driver', title: 'driver' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_FUELCARD');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getFuelcardCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/fuelcard?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'fuelcardCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Fuelcard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Fuelcard'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/fuelcard/fuelcard-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getFuelcardCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableFuelcard
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FuelcardTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default FuelcardTablesPage;
|
||||
140
frontend/src/pages/fuelcard/fuelcard-new.tsx
Normal file
140
frontend/src/pages/fuelcard/fuelcard-new.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiChartTimelineVariant,
|
||||
mdiMail,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement } 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 { 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 FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/fuelcard/fuelcardSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
organizations: '',
|
||||
|
||||
card_number: '',
|
||||
|
||||
limit: '',
|
||||
|
||||
vehicle: '',
|
||||
|
||||
driver: '',
|
||||
};
|
||||
|
||||
const FuelcardNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/fuelcard/fuelcard-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('New Item')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='New Item'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='organizations' labelFor='organizations'>
|
||||
<Field
|
||||
name='organizations'
|
||||
id='organizations'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'organizations'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Card number'>
|
||||
<Field name='card_number' placeholder='Card number' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Limit'>
|
||||
<Field type='number' name='limit' placeholder='Limit' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Vehicle' labelFor='vehicle'>
|
||||
<Field
|
||||
name='vehicle'
|
||||
id='vehicle'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'vehicle'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Driver' labelFor='driver'>
|
||||
<Field
|
||||
name='driver'
|
||||
id='driver'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'employees'}
|
||||
></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('/fuelcard/fuelcard-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FuelcardNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default FuelcardNew;
|
||||
168
frontend/src/pages/fuelcard/fuelcard-table.tsx
Normal file
168
frontend/src/pages/fuelcard/fuelcard-table.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } 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 TableFuelcard from '../../components/Fuelcard/TableFuelcard';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/fuelcard/fuelcardSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const FuelcardTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Card number', title: 'card_number' },
|
||||
{ label: 'Limit', title: 'limit', number: 'true' },
|
||||
|
||||
{ label: 'Vehicle', title: 'vehicle' },
|
||||
|
||||
{ label: 'Driver', title: 'driver' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_FUELCARD');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getFuelcardCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/fuelcard?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'fuelcardCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Fuelcard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Fuelcard'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/fuelcard/fuelcard-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getFuelcardCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableFuelcard
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={true}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FuelcardTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default FuelcardTablesPage;
|
||||
163
frontend/src/pages/fuelcard/fuelcard-view.tsx
Normal file
163
frontend/src/pages/fuelcard/fuelcard-view.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import Head from 'next/head';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/fuelcard/fuelcardSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import { getPageTitle } from '../../config';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const FuelcardView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { fuelcard } = useAppSelector((state) => state.fuelcard);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
function removeLastCharacter(str) {
|
||||
console.log(str, `str`);
|
||||
return str.slice(0, -1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View fuelcard')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View fuelcard')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/fuelcard/fuelcard-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>organizations</p>
|
||||
|
||||
<p>{fuelcard?.organizations?.name ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Card number</p>
|
||||
<p>{fuelcard?.card_number}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Limit</p>
|
||||
<p>{fuelcard?.limit || 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Vehicle</p>
|
||||
|
||||
<p>{fuelcard?.vehicle?.license_plate ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Driver</p>
|
||||
|
||||
<p>{fuelcard?.driver?.first_name ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Fuellog Fuel_card</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
|
||||
<th>Quantity</th>
|
||||
|
||||
<th>Unit cost</th>
|
||||
|
||||
<th>Total cost</th>
|
||||
|
||||
<th>Odometer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fuelcard.fuellog_fuel_card &&
|
||||
Array.isArray(fuelcard.fuellog_fuel_card) &&
|
||||
fuelcard.fuellog_fuel_card.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/fuellog/fuellog-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='date'>
|
||||
{dataFormatter.dateTimeFormatter(item.date)}
|
||||
</td>
|
||||
|
||||
<td data-label='quantity'>{item.quantity}</td>
|
||||
|
||||
<td data-label='unit_cost'>{item.unit_cost}</td>
|
||||
|
||||
<td data-label='total_cost'>{item.total_cost}</td>
|
||||
|
||||
<td data-label='odometer'>{item.odometer}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!fuelcard?.fuellog_fuel_card?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() => router.push('/fuelcard/fuelcard-list')}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FuelcardView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_FUELCARD'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default FuelcardView;
|
||||
@ -111,7 +111,7 @@ export default function WebSite() {
|
||||
<FeaturesSection
|
||||
projectName={'Transport Managment'}
|
||||
image={['Streamlined operations dashboard']}
|
||||
withBg={0}
|
||||
withBg={1}
|
||||
features={features_points}
|
||||
mainText={`Explore ${projectName} Key Features`}
|
||||
subTitle={`Discover how ${projectName} can transform your manufacturing operations with its powerful features.`}
|
||||
|
||||
@ -74,7 +74,7 @@ export default function WebSite() {
|
||||
<FeaturesSection
|
||||
projectName={'Transport Managment'}
|
||||
image={['Efficient workflow illustration']}
|
||||
withBg={0}
|
||||
withBg={1}
|
||||
features={features_points}
|
||||
mainText={`Unleash the Power of ${projectName}`}
|
||||
subTitle={`Explore the key features of ${projectName} that drive efficiency and innovation in your manufacturing operations.`}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user