Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
1f9e116dc4 Autosave: 20260216-190809 2026-02-16 19:08:10 +00:00
153 changed files with 10587 additions and 8556 deletions

View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class BreedersDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.breeders.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.breeders.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.breeders.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.breeders.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.breeders.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.breeders.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('breeders', 'name', filter.name);
}
const { rows, count } = await db.breeders.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('breeders', 'name', query),
];
}
const records = await db.breeders.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,142 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class ContainersDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.containers.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.containers.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.containers.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.containers.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.containers.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.containers.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('containers', 'name', filter.name);
}
const { rows, count } = await db.containers.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('containers', 'name', query),
];
}
const records = await db.containers.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,142 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Crop_typesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.crop_types.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.crop_types.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.crop_types.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.crop_types.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.crop_types.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.crop_types.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('crop_types', 'name', filter.name);
}
const { rows, count } = await db.crop_types.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('crop_types', 'name', query),
];
}
const records = await db.crop_types.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class groupsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.groups.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.groups.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.groups.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.groups.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.groups.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.groups.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('groups', 'name', filter.name);
}
const { rows, count } = await db.groups.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('groups', 'name', query),
];
}
const records = await db.groups.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,142 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Growth_habitsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.growth_habits.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.growth_habits.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.growth_habits.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.growth_habits.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.growth_habits.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.growth_habits.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('growth_habits', 'name', filter.name);
}
const { rows, count } = await db.growth_habits.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('growth_habits', 'name', query),
];
}
const records = await db.growth_habits.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class locationsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.locations.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.locations.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.locations.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.locations.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.locations.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.locations.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('locations', 'name', filter.name);
}
const { rows, count } = await db.locations.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('locations', 'name', query),
];
}
const records = await db.locations.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class programsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.programs.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.programs.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.programs.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.programs.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.programs.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.programs.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('programs', 'name', filter.name);
}
const { rows, count } = await db.programs.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('programs', 'name', query),
];
}
const records = await db.programs.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,142 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Propagation_typesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.propagation_types.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.propagation_types.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.propagation_types.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.propagation_types.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.propagation_types.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.propagation_types.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('propagation_types', 'name', filter.name);
}
const { rows, count } = await db.propagation_types.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('propagation_types', 'name', query),
];
}
const records = await db.propagation_types.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,142 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Retailer_programsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.retailer_programs.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.retailer_programs.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.retailer_programs.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.retailer_programs.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.retailer_programs.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.retailer_programs.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('retailer_programs', 'name', filter.name);
}
const { rows, count } = await db.retailer_programs.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('retailer_programs', 'name', query),
];
}
const records = await db.retailer_programs.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class retailersDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.retailers.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.retailers.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.retailers.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.retailers.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.retailers.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.retailers.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('retailers', 'name', filter.name);
}
const { rows, count } = await db.retailers.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('retailers', 'name', query),
];
}
const records = await db.retailers.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

141
backend/src/db/api/sites.js Normal file
View File

@ -0,0 +1,141 @@
const db = require('../models');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class sitesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.sites.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
tenantId: data.tenant || currentUser.tenantId || null,
organizationsId: data.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return record;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const recordsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
tenantId: item.tenant || currentUser.tenantId || null,
organizationsId: item.organizations || currentUser.organizationsId || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
return await db.sites.bulkCreate(recordsData, { transaction });
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const record = await db.sites.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await record.update(updatePayload, { transaction });
if (data.tenant !== undefined) await record.setTenant(data.tenant, { transaction });
if (data.organizations !== undefined) await record.setOrganizations(data.organizations, { transaction });
return record;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const records = await db.sites.findAll({
where: { id: { [Op.in]: ids } },
transaction,
});
for (const record of records) {
await record.destroy({ transaction });
}
return records;
}
static async remove(id, options) {
const transaction = (options && options.transaction) || undefined;
const record = await db.sites.findByPk(id, options);
await record.destroy({ transaction });
return record;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
return await db.sites.findOne({ where }, { transaction });
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
const currentPage = +filter.page || 0;
const offset = currentPage * limit;
let where = {};
const user = (options && options.currentUser) || null;
if (!globalAccess && user?.organizationsId) {
where.organizationsId = user.organizationsId;
}
if (filter) {
if (filter.id) where.id = Utils.uuid(filter.id);
if (filter.name) where[Op.and] = Utils.ilike('sites', 'name', filter.name);
}
const { rows, count } = await db.sites.findAndCountAll({
where,
include: [
{ model: db.tenants, as: 'tenant' },
{ model: db.organizations, as: 'organizations' }
],
distinct: true,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
transaction: options?.transaction,
});
return { rows, count };
}
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
let where = {};
if (!globalAccess && organizationId) where.organizationsId = organizationId;
if (query) {
where[Op.or] = [
{ id: Utils.uuid(query) },
Utils.ilike('sites', 'name', query),
];
}
const records = await db.sites.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['name', 'ASC']],
});
return records.map((record) => ({ id: record.id, label: record.name }));
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
require('dotenv').config();
module.exports = {
production: {
@ -12,11 +12,12 @@ module.exports = {
seederStorage: 'sequelize',
},
development: {
username: 'postgres',
dialect: 'postgres',
password: '',
database: 'db_trial_tracker',
host: process.env.DB_HOST || 'localhost',
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
logging: console.log,
seederStorage: 'sequelize',
},

View File

@ -0,0 +1,93 @@
module.exports = {
/**
* @param {QueryInterface} queryInterface
* @param {Sequelize} Sequelize
* @returns {Promise<void>}
*/
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
const tables = [
'propagation_types',
'growth_habits',
'crop_types',
'containers',
'retailer_programs'
];
for (const tableName of tables) {
await queryInterface.createTable(tableName, {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: Sequelize.DataTypes.TEXT,
allowNull: false,
},
tenantId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'tenants',
key: 'id',
},
},
organizationsId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'organizations',
key: 'id',
},
},
createdById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
updatedById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
deletedAt: { type: Sequelize.DataTypes.DATE },
importHash: {
type: Sequelize.DataTypes.STRING(255),
allowNull: true,
unique: true,
},
}, { transaction });
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
async down(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
const tables = [
'propagation_types',
'growth_habits',
'crop_types',
'containers',
'retailer_programs'
];
for (const tableName of tables) {
await queryInterface.dropTable(tableName, { transaction });
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
}
};

View File

@ -0,0 +1,138 @@
module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
// Create breeders table
await queryInterface.createTable('breeders', {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: Sequelize.DataTypes.TEXT,
allowNull: false,
},
tenantId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'tenants',
key: 'id',
},
},
organizationsId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'organizations',
key: 'id',
},
},
createdById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
updatedById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
deletedAt: { type: Sequelize.DataTypes.DATE },
importHash: {
type: Sequelize.DataTypes.STRING(255),
allowNull: true,
unique: true,
},
}, { transaction });
// Add columns to trials
await queryInterface.addColumn('trials', 'trial_code', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('trials', 'genus', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('trials', 'species', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('trials', 'colour', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('trials', 'breederId', {
type: Sequelize.DataTypes.UUID,
references: {
model: 'breeders',
key: 'id',
},
}, { transaction });
await queryInterface.addColumn('trials', 'propagationTypeId', {
type: Sequelize.DataTypes.UUID,
references: {
model: 'propagation_types',
key: 'id',
},
}, { transaction });
await queryInterface.addColumn('trials', 'cropTypeId', {
type: Sequelize.DataTypes.UUID,
references: {
model: 'crop_types',
key: 'id',
},
}, { transaction });
// Create join table for growth_habits (multi-select)
await queryInterface.createTable('trials_growth_habits', {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
trialId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'trials',
key: 'id',
},
allowNull: false,
onDelete: 'CASCADE',
},
growthHabitId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'growth_habits',
key: 'id',
},
allowNull: false,
onDelete: 'CASCADE',
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
}, { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
async down(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.dropTable('trials_growth_habits', { transaction });
await queryInterface.removeColumn('trials', 'cropTypeId', { transaction });
await queryInterface.removeColumn('trials', 'propagationTypeId', { transaction });
await queryInterface.removeColumn('trials', 'breederId', { transaction });
await queryInterface.removeColumn('trials', 'colour', { transaction });
await queryInterface.removeColumn('trials', 'species', { transaction });
await queryInterface.removeColumn('trials', 'genus', { transaction });
await queryInterface.removeColumn('trials', 'trial_code', { transaction });
await queryInterface.dropTable('breeders', { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
}
};

View File

@ -0,0 +1,200 @@
module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
const tablesToCreate = ['retailers', 'programs', 'groups', 'sites', 'locations'];
for (const tableName of tablesToCreate) {
// Check if table exists
const tableExists = await queryInterface.describeTable(tableName).then(() => true).catch(() => false);
if (!tableExists) {
await queryInterface.createTable(tableName, {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: Sequelize.DataTypes.TEXT,
allowNull: false,
},
tenantId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'tenants',
key: 'id',
},
},
organizationsId: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'organizations',
key: 'id',
},
},
createdById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
updatedById: {
type: Sequelize.DataTypes.UUID,
references: {
model: 'users',
key: 'id',
},
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
deletedAt: { type: Sequelize.DataTypes.DATE },
importHash: {
type: Sequelize.DataTypes.STRING(255),
allowNull: true,
unique: true,
},
}, { transaction });
}
}
// Add columns to trials
const trialsColumns = await queryInterface.describeTable('trials');
const addColumnIfMissing = async (table, column, definition) => {
if (!trialsColumns[column]) {
await queryInterface.addColumn(table, column, definition, { transaction });
}
};
await addColumnIfMissing('trials', 'containerSize', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'finishedHeight', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'finishedSpread', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'description', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'internalReferenceNumber', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'programYear', { type: Sequelize.DataTypes.INTEGER });
await addColumnIfMissing('trials', 'distributor', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'quantity', { type: Sequelize.DataTypes.DECIMAL });
await addColumnIfMissing('trials', 'receiveWeek', { type: Sequelize.DataTypes.INTEGER });
await addColumnIfMissing('trials', 'finishWeek', { type: Sequelize.DataTypes.INTEGER });
await addColumnIfMissing('trials', 'tableId', { type: Sequelize.DataTypes.TEXT });
await addColumnIfMissing('trials', 'containerId', {
type: Sequelize.DataTypes.UUID,
references: { model: 'containers', key: 'id' },
});
await addColumnIfMissing('trials', 'cultureSheetId', {
type: Sequelize.DataTypes.UUID,
references: { model: 'files', key: 'id' },
});
await addColumnIfMissing('trials', 'programId', {
type: Sequelize.DataTypes.UUID,
references: { model: 'programs', key: 'id' },
});
await addColumnIfMissing('trials', 'siteId', {
type: Sequelize.DataTypes.UUID,
references: { model: 'sites', key: 'id' },
});
await addColumnIfMissing('trials', 'locationId', {
type: Sequelize.DataTypes.UUID,
references: { model: 'locations', key: 'id' },
});
// Create junction tables for multi-select
const retailersJunctionExists = await queryInterface.describeTable('trials_retailers').then(() => true).catch(() => false);
if (!retailersJunctionExists) {
await queryInterface.createTable('trials_retailers', {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
trialId: {
type: Sequelize.DataTypes.UUID,
references: { model: 'trials', key: 'id' },
allowNull: false,
onDelete: 'CASCADE',
},
retailerId: {
type: Sequelize.DataTypes.UUID,
references: { model: 'retailers', key: 'id' },
allowNull: false,
onDelete: 'CASCADE',
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
}, { transaction });
}
const groupsJunctionExists = await queryInterface.describeTable('trials_groups').then(() => true).catch(() => false);
if (!groupsJunctionExists) {
await queryInterface.createTable('trials_groups', {
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
trialId: {
type: Sequelize.DataTypes.UUID,
references: { model: 'trials', key: 'id' },
allowNull: false,
onDelete: 'CASCADE',
},
groupId: {
type: Sequelize.DataTypes.UUID,
references: { model: 'groups', key: 'id' },
allowNull: false,
onDelete: 'CASCADE',
},
createdAt: { type: Sequelize.DataTypes.DATE },
updatedAt: { type: Sequelize.DataTypes.DATE },
}, { transaction });
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
async down(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.dropTable('trials_groups', { transaction });
await queryInterface.dropTable('trials_retailers', { transaction });
await queryInterface.removeColumn('trials', 'locationId', { transaction });
await queryInterface.removeColumn('trials', 'siteId', { transaction });
await queryInterface.removeColumn('trials', 'programId', { transaction });
await queryInterface.removeColumn('trials', 'cultureSheetId', { transaction });
await queryInterface.removeColumn('trials', 'containerId', { transaction });
await queryInterface.removeColumn('trials', 'tableId', { transaction });
await queryInterface.removeColumn('trials', 'finishWeek', { transaction });
await queryInterface.removeColumn('trials', 'receiveWeek', { transaction });
await queryInterface.removeColumn('trials', 'quantity', { transaction });
await queryInterface.removeColumn('trials', 'distributor', { transaction });
await queryInterface.removeColumn('trials', 'programYear', { transaction });
await queryInterface.removeColumn('trials', 'internalReferenceNumber', { transaction });
await queryInterface.removeColumn('trials', 'description', { transaction });
await queryInterface.removeColumn('trials', 'finishedSpread', { transaction });
await queryInterface.removeColumn('trials', 'finishedHeight', { transaction });
await queryInterface.removeColumn('trials', 'containerSize', { transaction });
const tablesToDrop = ['retailers', 'programs', 'groups', 'sites', 'locations'];
for (const tableName of tablesToDrop) {
await queryInterface.dropTable(tableName, { transaction });
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
}
};

View File

@ -0,0 +1,21 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('trials', 'attributes', {
type: Sequelize.JSONB,
allowNull: true,
defaultValue: {},
});
await queryInterface.addColumn('trials', 'trial_category', {
type: Sequelize.STRING,
allowNull: false,
defaultValue: 'PLANT',
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn('trials', 'attributes');
await queryInterface.removeColumn('trials', 'trial_category');
},
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const breeders = sequelize.define(
'breeders',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
breeders.associate = (db) => {
db.breeders.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.breeders.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.breeders.belongsTo(db.users, {
as: 'createdBy',
});
db.breeders.belongsTo(db.users, {
as: 'updatedBy',
});
};
return breeders;
};

View File

@ -0,0 +1,55 @@
module.exports = function(sequelize, DataTypes) {
const containers = sequelize.define(
'containers',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
containers.associate = (db) => {
db.containers.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.containers.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.containers.belongsTo(db.users, {
as: 'createdBy',
});
db.containers.belongsTo(db.users, {
as: 'updatedBy',
});
};
return containers;
};

View File

@ -0,0 +1,55 @@
module.exports = function(sequelize, DataTypes) {
const crop_types = sequelize.define(
'crop_types',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
crop_types.associate = (db) => {
db.crop_types.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.crop_types.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.crop_types.belongsTo(db.users, {
as: 'createdBy',
});
db.crop_types.belongsTo(db.users, {
as: 'updatedBy',
});
};
return crop_types;
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const groups = sequelize.define(
'groups',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
groups.associate = (db) => {
db.groups.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.groups.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.groups.belongsTo(db.users, {
as: 'createdBy',
});
db.groups.belongsTo(db.users, {
as: 'updatedBy',
});
};
return groups;
};

View File

@ -0,0 +1,55 @@
module.exports = function(sequelize, DataTypes) {
const growth_habits = sequelize.define(
'growth_habits',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
growth_habits.associate = (db) => {
db.growth_habits.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.growth_habits.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.growth_habits.belongsTo(db.users, {
as: 'createdBy',
});
db.growth_habits.belongsTo(db.users, {
as: 'updatedBy',
});
};
return growth_habits;
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const locations = sequelize.define(
'locations',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
locations.associate = (db) => {
db.locations.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.locations.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.locations.belongsTo(db.users, {
as: 'createdBy',
});
db.locations.belongsTo(db.users, {
as: 'updatedBy',
});
};
return locations;
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const programs = sequelize.define(
'programs',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
programs.associate = (db) => {
db.programs.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.programs.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.programs.belongsTo(db.users, {
as: 'createdBy',
});
db.programs.belongsTo(db.users, {
as: 'updatedBy',
});
};
return programs;
};

View File

@ -0,0 +1,55 @@
module.exports = function(sequelize, DataTypes) {
const propagation_types = sequelize.define(
'propagation_types',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
propagation_types.associate = (db) => {
db.propagation_types.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.propagation_types.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.propagation_types.belongsTo(db.users, {
as: 'createdBy',
});
db.propagation_types.belongsTo(db.users, {
as: 'updatedBy',
});
};
return propagation_types;
};

View File

@ -0,0 +1,55 @@
module.exports = function(sequelize, DataTypes) {
const retailer_programs = sequelize.define(
'retailer_programs',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
retailer_programs.associate = (db) => {
db.retailer_programs.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.retailer_programs.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.retailer_programs.belongsTo(db.users, {
as: 'createdBy',
});
db.retailer_programs.belongsTo(db.users, {
as: 'updatedBy',
});
};
return retailer_programs;
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const retailers = sequelize.define(
'retailers',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
retailers.associate = (db) => {
db.retailers.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.retailers.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.retailers.belongsTo(db.users, {
as: 'createdBy',
});
db.retailers.belongsTo(db.users, {
as: 'updatedBy',
});
};
return retailers;
};

View File

@ -0,0 +1,54 @@
module.exports = function(sequelize, DataTypes) {
const sites = sequelize.define(
'sites',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
allowNull: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
sites.associate = (db) => {
db.sites.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
name: 'tenantId',
},
constraints: false,
});
db.sites.belongsTo(db.organizations, {
as: 'organizations',
foreignKey: {
name: 'organizationsId',
},
constraints: false,
});
db.sites.belongsTo(db.users, {
as: 'createdBy',
});
db.sites.belongsTo(db.users, {
as: 'updatedBy',
});
};
return sites;
};

View File

@ -1,8 +1,4 @@
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 trials = sequelize.define(
@ -13,165 +9,122 @@ module.exports = function(sequelize, DataTypes) {
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
name: {
type: DataTypes.TEXT,
},
variety_name: {
trial_code: {
type: DataTypes.TEXT,
},
breeder: {
variety_name: {
type: DataTypes.TEXT,
},
batch_code: {
genus: {
type: DataTypes.TEXT,
},
trial_type: {
species: {
type: DataTypes.TEXT,
},
colour: {
type: DataTypes.TEXT,
},
batch_code: {
type: DataTypes.TEXT,
},
trial_type: {
type: DataTypes.ENUM,
values: [
"new_variety",
"performance",
"disease_resistance",
"yield",
"quality",
"other"
"new_variety",
"performance",
"disease_resistance",
"yield",
"quality",
"other"
],
},
status: {
status: {
type: DataTypes.ENUM,
values: [
"setup",
"in_progress",
"paused",
"completed",
"archived"
"setup",
"in_progress",
"paused",
"completed",
"archived"
],
},
planted_at: {
planted_at: {
type: DataTypes.DATE,
},
harvested_at: {
harvested_at: {
type: DataTypes.DATE,
},
greenhouse: {
greenhouse: {
type: DataTypes.TEXT,
},
zone: {
zone: {
type: DataTypes.TEXT,
},
bench: {
bench: {
type: DataTypes.TEXT,
},
plants_count: {
plants_count: {
type: DataTypes.INTEGER,
},
target_temperature_c: {
target_temperature_c: {
type: DataTypes.DECIMAL,
},
target_humidity_percent: {
target_humidity_percent: {
type: DataTypes.DECIMAL,
},
target_ec: {
target_ec: {
type: DataTypes.DECIMAL,
},
target_ph: {
target_ph: {
type: DataTypes.DECIMAL,
},
notes: {
notes: {
type: DataTypes.TEXT,
},
containerSize: {
type: DataTypes.TEXT,
},
finishedHeight: {
type: DataTypes.TEXT,
},
finishedSpread: {
type: DataTypes.TEXT,
},
description: {
type: DataTypes.TEXT,
},
internalReferenceNumber: {
type: DataTypes.TEXT,
},
programYear: {
type: DataTypes.INTEGER,
},
distributor: {
type: DataTypes.TEXT,
},
quantity: {
type: DataTypes.DECIMAL,
},
receiveWeek: {
type: DataTypes.INTEGER,
},
finishWeek: {
type: DataTypes.INTEGER,
},
tableId: {
type: DataTypes.TEXT,
},
attributes: {
type: DataTypes.JSONB,
defaultValue: {},
},
trial_category: {
type: DataTypes.STRING,
defaultValue: 'PLANT',
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
@ -186,19 +139,66 @@ notes: {
);
trials.associate = (db) => {
db.trials.belongsTo(db.breeders, {
as: 'breeder',
foreignKey: 'breederId',
});
db.trials.belongsTo(db.propagation_types, {
as: 'propagationType',
foreignKey: 'propagationTypeId',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.trials.belongsTo(db.crop_types, {
as: 'cropType',
foreignKey: 'cropTypeId',
});
db.trials.belongsToMany(db.growth_habits, {
as: 'growthHabits',
through: 'trials_growth_habits',
foreignKey: 'trialId',
otherKey: 'growthHabitId',
});
db.trials.belongsTo(db.containers, {
as: 'container',
foreignKey: 'containerId',
});
db.trials.belongsTo(db.file, {
as: 'cultureSheet',
foreignKey: 'cultureSheetId',
});
db.trials.belongsTo(db.programs, {
as: 'program',
foreignKey: 'programId',
});
db.trials.belongsTo(db.sites, {
as: 'site',
foreignKey: 'siteId',
});
db.trials.belongsTo(db.locations, {
as: 'location',
foreignKey: 'locationId',
});
db.trials.belongsToMany(db.retailers, {
as: 'retailers',
through: 'trials_retailers',
foreignKey: 'trialId',
otherKey: 'retailerId',
});
db.trials.belongsToMany(db.groups, {
as: 'groups',
through: 'trials_groups',
foreignKey: 'trialId',
otherKey: 'groupId',
});
db.trials.hasMany(db.tracking_activities, {
as: 'tracking_activities_trial',
@ -208,16 +208,6 @@ notes: {
constraints: false,
});
//end loop
db.trials.belongsTo(db.tenants, {
as: 'tenant',
foreignKey: {
@ -242,9 +232,6 @@ notes: {
constraints: false,
});
db.trials.belongsTo(db.users, {
as: 'createdBy',
});
@ -254,9 +241,5 @@ notes: {
});
};
return trials;
};

View File

@ -0,0 +1,67 @@
const { v4: uuid } = require("uuid");
module.exports = {
async up(queryInterface) {
const createdAt = new Date();
const updatedAt = new Date();
const entities = [
"propagation_types",
"growth_habits",
"crop_types",
"containers",
"retailer_programs"
];
const permissions = [];
for (const entity of entities) {
const upperName = entity.toUpperCase();
permissions.push(
{ id: uuid(), name: `CREATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `READ_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `UPDATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `DELETE_${upperName}`, createdAt, updatedAt }
);
}
await queryInterface.bulkInsert("permissions", permissions);
const [roles] = await queryInterface.sequelize.query(
`SELECT id, name FROM roles WHERE name IN ('Super Administrator', 'Administrator', 'Platform Owner', 'Tenant Owner', 'Trial Manager');`
);
const [dbPermissions] = await queryInterface.sequelize.query(
`SELECT id, name FROM permissions WHERE name LIKE 'CREATE_%' OR name LIKE 'READ_%' OR name LIKE 'UPDATE_%' OR name LIKE 'DELETE_%';`
);
const rolesPermissions = [];
for (const role of roles) {
for (const permission of dbPermissions) {
// Only add if it's one of our new permissions
const isNewPermission = entities.some(e => permission.name.includes(e.toUpperCase()));
if (isNewPermission) {
rolesPermissions.push({
createdAt,
updatedAt,
roles_permissionsId: role.id,
permissionId: permission.id
});
}
}
}
// Use a try-catch or IGNORE to avoid duplicates if seeder runs multiple times
for (const rp of rolesPermissions) {
try {
await queryInterface.bulkInsert("rolesPermissionsPermissions", [rp]);
} catch (e) {
// Likely duplicate key
}
}
},
async down(queryInterface) {
// Optional: cleanup
}
};

View File

@ -0,0 +1,51 @@
const { v4: uuid } = require("uuid");
module.exports = {
async up(queryInterface) {
const createdAt = new Date();
const updatedAt = new Date();
const entity = "breeders";
const upperName = entity.toUpperCase();
const permissions = [
{ id: uuid(), name: `CREATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `READ_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `UPDATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `DELETE_${upperName}`, createdAt, updatedAt }
];
await queryInterface.bulkInsert("permissions", permissions);
const [roles] = await queryInterface.sequelize.query(
`SELECT id, name FROM roles WHERE name IN ('Super Administrator', 'Administrator', 'Platform Owner', 'Tenant Owner', 'Trial Manager');`
);
const [dbPermissions] = await queryInterface.sequelize.query(
`SELECT id, name FROM permissions WHERE name LIKE '%BREEDERS%';`
);
const rolesPermissions = [];
for (const role of roles) {
for (const permission of dbPermissions) {
rolesPermissions.push({
createdAt,
updatedAt,
roles_permissionsId: role.id,
permissionId: permission.id
});
}
}
for (const rp of rolesPermissions) {
try {
await queryInterface.bulkInsert("rolesPermissionsPermissions", [rp]);
} catch (e) {
// Likely duplicate key
}
}
},
async down(queryInterface) {
}
};

View File

@ -0,0 +1,54 @@
const { v4: uuid } = require("uuid");
module.exports = {
async up(queryInterface) {
const createdAt = new Date();
const updatedAt = new Date();
const entities = ["retailers", "programs", "groups", "sites", "locations"];
for (const entity of entities) {
const upperName = entity.toUpperCase();
const permissions = [
{ id: uuid(), name: `CREATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `READ_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `UPDATE_${upperName}`, createdAt, updatedAt },
{ id: uuid(), name: `DELETE_${upperName}`, createdAt, updatedAt }
];
await queryInterface.bulkInsert("permissions", permissions);
const [roles] = await queryInterface.sequelize.query(
`SELECT id, name FROM roles WHERE name IN ('Super Administrator', 'Administrator', 'Platform Owner', 'Tenant Owner', 'Trial Manager');`
);
const [dbPermissions] = await queryInterface.sequelize.query(
`SELECT id, name FROM permissions WHERE name LIKE '%${upperName}%';`
);
const rolesPermissions = [];
for (const role of roles) {
for (const permission of dbPermissions) {
rolesPermissions.push({
createdAt,
updatedAt,
roles_permissionsId: role.id,
permissionId: permission.id
});
}
}
for (const rp of rolesPermissions) {
try {
await queryInterface.bulkInsert("rolesPermissionsPermissions", [rp]);
} catch (e) {
// Likely duplicate key
}
}
}
},
async down(queryInterface) {
}
};

View File

@ -1,4 +1,3 @@
const express = require('express');
const cors = require('cors');
const app = express();
@ -16,41 +15,36 @@ const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');
const pexelsRoutes = require('./routes/pexels');
const organizationForAuthRoutes = require('./routes/organizationLogin');
const openaiRoutes = require('./routes/openai');
const usersRoutes = require('./routes/users');
const rolesRoutes = require('./routes/roles');
const permissionsRoutes = require('./routes/permissions');
const organizationsRoutes = require('./routes/organizations');
const tenantsRoutes = require('./routes/tenants');
const impersonation_sessionsRoutes = require('./routes/impersonation_sessions');
const projectsRoutes = require('./routes/projects');
const trialsRoutes = require('./routes/trials');
const tracking_activity_typesRoutes = require('./routes/tracking_activity_types');
const tracking_activitiesRoutes = require('./routes/tracking_activities');
const dashboardsRoutes = require('./routes/dashboards');
const report_definitionsRoutes = require('./routes/report_definitions');
const report_runsRoutes = require('./routes/report_runs');
const tenant_settingsRoutes = require('./routes/tenant_settings');
const propagation_typesRoutes = require('./routes/propagation_types');
const growth_habitsRoutes = require('./routes/growth_habits');
const crop_typesRoutes = require('./routes/crop_types');
const containersRoutes = require('./routes/containers');
const retailer_programsRoutes = require('./routes/retailer_programs');
const breedersRoutes = require('./routes/breeders');
const retailersRoutes = require('./routes/retailers');
const programsRoutes = require('./routes/programs');
const groupsRoutes = require('./routes/groups');
const sitesRoutes = require('./routes/sites');
const locationsRoutes = require('./routes/locations');
const getBaseUrl = (url) => {
if (!url) return '';
@ -110,33 +104,32 @@ app.enable('trust proxy');
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);
app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes);
app.use('/api/organizations', passport.authenticate('jwt', {session: false}), organizationsRoutes);
app.use('/api/tenants', passport.authenticate('jwt', {session: false}), tenantsRoutes);
app.use('/api/impersonation_sessions', passport.authenticate('jwt', {session: false}), impersonation_sessionsRoutes);
app.use('/api/projects', passport.authenticate('jwt', {session: false}), projectsRoutes);
app.use('/api/trials', passport.authenticate('jwt', {session: false}), trialsRoutes);
app.use('/api/tracking_activity_types', passport.authenticate('jwt', {session: false}), tracking_activity_typesRoutes);
app.use('/api/tracking_activities', passport.authenticate('jwt', {session: false}), tracking_activitiesRoutes);
app.use('/api/dashboards', passport.authenticate('jwt', {session: false}), dashboardsRoutes);
app.use('/api/report_definitions', passport.authenticate('jwt', {session: false}), report_definitionsRoutes);
app.use('/api/report_runs', passport.authenticate('jwt', {session: false}), report_runsRoutes);
app.use('/api/tenant_settings', passport.authenticate('jwt', {session: false}), tenant_settingsRoutes);
app.use('/api/propagation_types', passport.authenticate('jwt', {session: false}), propagation_typesRoutes);
app.use('/api/growth_habits', passport.authenticate('jwt', {session: false}), growth_habitsRoutes);
app.use('/api/crop_types', passport.authenticate('jwt', {session: false}), crop_typesRoutes);
app.use('/api/containers', passport.authenticate('jwt', {session: false}), containersRoutes);
app.use('/api/retailer_programs', passport.authenticate('jwt', {session: false}), retailer_programsRoutes);
app.use('/api/breeders', passport.authenticate('jwt', {session: false}), breedersRoutes);
app.use('/api/retailers', passport.authenticate('jwt', {session: false}), retailersRoutes);
app.use('/api/programs', passport.authenticate('jwt', {session: false}), programsRoutes);
app.use('/api/groups', passport.authenticate('jwt', {session: false}), groupsRoutes);
app.use('/api/sites', passport.authenticate('jwt', {session: false}), sitesRoutes);
app.use('/api/locations', passport.authenticate('jwt', {session: false}), locationsRoutes);
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),

View File

@ -0,0 +1,53 @@
const express = require('express');
const BreedersService = require('../services/breeders');
const BreedersDBApi = require('../db/api/breeders');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('breeders'));
router.post('/', wrapAsync(async (req, res) => {
await BreedersService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await BreedersService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await BreedersService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await BreedersService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await BreedersDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await BreedersDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await BreedersDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,54 @@
const express = require('express');
const ContainersService = require('../services/containers');
const ContainersDBApi = require('../db/api/containers');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('containers'));
router.post('/', wrapAsync(async (req, res) => {
await ContainersService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await ContainersService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await ContainersService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await ContainersService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await ContainersDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await ContainersDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ContainersDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,54 @@
const express = require('express');
const Crop_typesService = require('../services/crop_types');
const Crop_typesDBApi = require('../db/api/crop_types');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('crop_types'));
router.post('/', wrapAsync(async (req, res) => {
await Crop_typesService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await Crop_typesService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await Crop_typesService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Crop_typesService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Crop_typesDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await Crop_typesDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Crop_typesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,53 @@
const express = require('express');
const groupsService = require('../services/groups');
const groupsDBApi = require('../db/api/groups');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('groups'));
router.post('/', wrapAsync(async (req, res) => {
await groupsService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await groupsService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await groupsService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await groupsService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await groupsDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await groupsDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await groupsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,54 @@
const express = require('express');
const Growth_habitsService = require('../services/growth_habits');
const Growth_habitsDBApi = require('../db/api/growth_habits');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('growth_habits'));
router.post('/', wrapAsync(async (req, res) => {
await Growth_habitsService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await Growth_habitsService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await Growth_habitsService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Growth_habitsService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Growth_habitsDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await Growth_habitsDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Growth_habitsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,53 @@
const express = require('express');
const locationsService = require('../services/locations');
const locationsDBApi = require('../db/api/locations');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('locations'));
router.post('/', wrapAsync(async (req, res) => {
await locationsService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await locationsService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await locationsService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await locationsService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await locationsDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await locationsDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await locationsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,53 @@
const express = require('express');
const programsService = require('../services/programs');
const programsDBApi = require('../db/api/programs');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('programs'));
router.post('/', wrapAsync(async (req, res) => {
await programsService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await programsService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await programsService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await programsService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await programsDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await programsDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await programsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,54 @@
const express = require('express');
const Propagation_typesService = require('../services/propagation_types');
const Propagation_typesDBApi = require('../db/api/propagation_types');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('propagation_types'));
router.post('/', wrapAsync(async (req, res) => {
await Propagation_typesService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await Propagation_typesService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await Propagation_typesService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Propagation_typesService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Propagation_typesDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await Propagation_typesDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Propagation_typesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,54 @@
const express = require('express');
const Retailer_programsService = require('../services/retailer_programs');
const Retailer_programsDBApi = require('../db/api/retailer_programs');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('retailer_programs'));
router.post('/', wrapAsync(async (req, res) => {
await Retailer_programsService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await Retailer_programsService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await Retailer_programsService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Retailer_programsService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Retailer_programsDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await Retailer_programsDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Retailer_programsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,53 @@
const express = require('express');
const retailersService = require('../services/retailers');
const retailersDBApi = require('../db/api/retailers');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('retailers'));
router.post('/', wrapAsync(async (req, res) => {
await retailersService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await retailersService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await retailersService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await retailersService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await retailersDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await retailersDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await retailersDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,53 @@
const express = require('express');
const sitesService = require('../services/sites');
const sitesDBApi = require('../db/api/sites');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('sites'));
router.post('/', wrapAsync(async (req, res) => {
await sitesService.create(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.put('/:id', wrapAsync(async (req, res) => {
await sitesService.update(req.body.data, req.body.id, req.currentUser);
res.status(200).send(true);
}));
router.delete('/:id', wrapAsync(async (req, res) => {
await sitesService.remove(req.params.id, req.currentUser);
res.status(200).send(true);
}));
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await sitesService.deleteByIds(req.body.data, req.currentUser);
res.status(200).send(true);
}));
router.get('/', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await sitesDBApi.findAll(req.query, globalAccess, { currentUser });
res.status(200).send(payload);
}));
router.get('/autocomplete', wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organizationsId;
const payload = await sitesDBApi.findAllAutocomplete(
req.query.query, req.query.limit, req.query.offset, globalAccess, organizationId
);
res.status(200).send(payload);
}));
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await sitesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const BreedersDBApi = require('../db/api/breeders');
const ValidationError = require('./notifications/errors/validation');
module.exports = class BreedersService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await BreedersDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await BreedersDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('breedersNotFound');
const updated = await BreedersDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await BreedersDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await BreedersDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,53 @@
const db = require('../db/models');
const ContainersDBApi = require('../db/api/containers');
const ValidationError = require('./notifications/errors/validation');
module.exports = class ContainersService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ContainersDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await ContainersDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('containersNotFound');
const updated = await ContainersDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ContainersDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ContainersDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,53 @@
const db = require('../db/models');
const Crop_typesDBApi = require('../db/api/crop_types');
const ValidationError = require('./notifications/errors/validation');
module.exports = class Crop_typesService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Crop_typesDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await Crop_typesDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('crop_typesNotFound');
const updated = await Crop_typesDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Crop_typesDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Crop_typesDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const groupsDBApi = require('../db/api/groups');
const ValidationError = require('./notifications/errors/validation');
module.exports = class groupsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await groupsDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await groupsDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('groupsNotFound');
const updated = await groupsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await groupsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await groupsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,53 @@
const db = require('../db/models');
const Growth_habitsDBApi = require('../db/api/growth_habits');
const ValidationError = require('./notifications/errors/validation');
module.exports = class Growth_habitsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Growth_habitsDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await Growth_habitsDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('growth_habitsNotFound');
const updated = await Growth_habitsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Growth_habitsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Growth_habitsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const locationsDBApi = require('../db/api/locations');
const ValidationError = require('./notifications/errors/validation');
module.exports = class locationsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await locationsDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await locationsDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('locationsNotFound');
const updated = await locationsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await locationsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await locationsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const programsDBApi = require('../db/api/programs');
const ValidationError = require('./notifications/errors/validation');
module.exports = class programsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await programsDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await programsDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('programsNotFound');
const updated = await programsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await programsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await programsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,53 @@
const db = require('../db/models');
const Propagation_typesDBApi = require('../db/api/propagation_types');
const ValidationError = require('./notifications/errors/validation');
module.exports = class Propagation_typesService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Propagation_typesDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await Propagation_typesDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('propagation_typesNotFound');
const updated = await Propagation_typesDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Propagation_typesDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Propagation_typesDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,53 @@
const db = require('../db/models');
const Retailer_programsDBApi = require('../db/api/retailer_programs');
const ValidationError = require('./notifications/errors/validation');
module.exports = class Retailer_programsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Retailer_programsDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await Retailer_programsDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('retailer_programsNotFound');
const updated = await Retailer_programsDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Retailer_programsDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Retailer_programsDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const retailersDBApi = require('../db/api/retailers');
const ValidationError = require('./notifications/errors/validation');
module.exports = class retailersService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await retailersDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await retailersDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('retailersNotFound');
const updated = await retailersDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await retailersDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await retailersDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -0,0 +1,52 @@
const db = require('../db/models');
const sitesDBApi = require('../db/api/sites');
const ValidationError = require('./notifications/errors/validation');
module.exports = class sitesService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await sitesDBApi.create(data, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
const record = await sitesDBApi.findBy({ id }, { transaction });
if (!record) throw new ValidationError('sitesNotFound');
const updated = await sitesDBApi.update(id, data, { currentUser, transaction });
await transaction.commit();
return updated;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await sitesDBApi.deleteByIds(ids, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await sitesDBApi.remove(id, { currentUser, transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks'
import { useAppSelector, useAppDispatch } from '../stores/hooks'
import Link from 'next/link';
import { useAppDispatch } from '../stores/hooks';
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/breeders/breedersSlice'
import { loadColumns } from "./configureBreedersCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableBreeders = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { breeders, loading, count, refetch } = useAppSelector((state) => state.breeders);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={breeders || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableBreeders;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_BREEDERS')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/breeders/breeders-edit/?id=${params?.row?.id}`}
pathView={`/breeders/breeders-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/containers/containersSlice'
import { loadColumns } from "./configureContainersCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableContainers = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { containers, loading, count, refetch } = useAppSelector((state) => state.containers);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton color="danger" label="Delete selected" icon={mdiDelete} onClick={() => setIsDeleteModalActive(true)} />,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={containers || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableContainers;

View File

@ -0,0 +1,30 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_CONTAINERS')
const hasDeletePermission = hasPermission(user, 'DELETE_CONTAINERS')
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
params={params}
pathEdit={`/containers/containers-edit/?id=${params?.row?.id}`}
pathView={`/containers/containers-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
hasDeletePermission={hasDeletePermission}
deleteHandler={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/crop_types/crop_typesSlice'
import { loadColumns } from "./configureCrop_typesCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableCrop_types = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { crop_types, loading, count, refetch } = useAppSelector((state) => state.crop_types);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton color="danger" label="Delete selected" icon={mdiDelete} onClick={() => setIsDeleteModalActive(true)} />,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={crop_types || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableCrop_types;

View File

@ -0,0 +1,30 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_CROP_TYPES')
const hasDeletePermission = hasPermission(user, 'DELETE_CROP_TYPES')
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
params={params}
pathEdit={`/crop_types/crop_types-edit/?id=${params?.row?.id}`}
pathView={`/crop_types/crop_types-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
hasDeletePermission={hasDeletePermission}
deleteHandler={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/groups/groupsSlice'
import { loadColumns } from "./configureGroupsCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableGroups = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { groups, loading, count, refetch } = useAppSelector((state) => state.groups);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={groups || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableGroups;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_GROUPS')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/groups/groups-edit/?id=${params?.row?.id}`}
pathView={`/groups/groups-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/growth_habits/growth_habitsSlice'
import { loadColumns } from "./configureGrowth_habitsCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableGrowth_habits = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { growth_habits, loading, count, refetch } = useAppSelector((state) => state.growth_habits);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton color="danger" label="Delete selected" icon={mdiDelete} onClick={() => setIsDeleteModalActive(true)} />,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={growth_habits || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableGrowth_habits;

View File

@ -0,0 +1,31 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_GROWTH_HABITS')
const hasDeletePermission = hasPermission(user, 'DELETE_GROWTH_HABITS')
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
params={params}
pathEdit={`/growth_habits/growth_habits-edit/?id=${params?.row?.id}`}
pathView={`/growth_habits/growth_habits-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
hasDeletePermission={hasDeletePermission}
deleteHandler={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/locations/locationsSlice'
import { loadColumns } from "./configureLocationsCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableLocations = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { locations, loading, count, refetch } = useAppSelector((state) => state.locations);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={locations || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableLocations;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_LOCATIONS')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/locations/locations-edit/?id=${params?.row?.id}`}
pathView={`/locations/locations-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -9,11 +9,12 @@ import { useAppSelector } from '../stores/hooks';
type Props = {
menu: MenuNavBarItem[]
leftMenu?: MenuNavBarItem[]
className: string
children: ReactNode
}
export default function NavBar({ menu, className = '', children }: Props) {
export default function NavBar({ menu, leftMenu = [], className = '', children }: Props) {
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false)
const [isScrolled, setIsScrolled] = useState(false);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -38,7 +39,12 @@ export default function NavBar({ menu, className = '', children }: Props) {
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 transition-position w-screen lg:w-auto dark:bg-dark-800`}
>
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-pavitra-400 dark:border-dark-700`}`}>
<div className="flex flex-1 items-stretch h-14">{children}</div>
<div className="flex flex-1 items-stretch h-14">
<div className="hidden lg:flex items-stretch overflow-x-auto aside-scrollbars">
<NavBarMenuList menu={leftMenu} />
</div>
{children}
</div>
<div className="flex-none items-stretch flex h-14 lg:hidden">
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
<BaseIcon path={isMenuNavBarActive ? mdiClose : mdiDotsVertical} size="24" />
@ -49,6 +55,9 @@ export default function NavBar({ menu, className = '', children }: Props) {
isMenuNavBarActive ? 'block' : 'hidden'
} flex items-center max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 ${bgColor} shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-dark-800`}
>
<div className="lg:hidden">
<NavBarMenuList menu={leftMenu} />
</div>
<NavBarMenuList menu={menu} />
</div>
</div>

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react'
import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@ -39,17 +38,21 @@ export default function NavBarItem({ item }: Props) {
}, [router.pathname]);
const componentClass = [
'block lg:flex items-center relative cursor-pointer',
'block lg:flex items-center relative',
item.isHeader ? 'cursor-default' : 'cursor-pointer',
isDropdownActive
? `${navBarItemLabelActiveColorStyle} dark:text-slate-400`
: `${navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${navBarItemLabelHoverStyle}`,
: `${navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${item.isHeader ? '' : navBarItemLabelHoverStyle}`,
item.menu ? 'lg:py-2 lg:px-3' : 'py-2 px-3',
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
item.isHeader ? 'text-xs uppercase font-bold text-gray-400 dark:text-slate-500 mt-2' : ''
].join(' ')
const itemLabel = item.isCurrentUser ? userName : item.label
const handleMenuClick = () => {
if (item.isHeader) return
if (item.menu) {
setIsDropdownActive(!isDropdownActive)
}
@ -86,9 +89,9 @@ export default function NavBarItem({ item }: Props) {
}`}
onClick={handleMenuClick}
>
{item.icon && <BaseIcon path={item.icon} size={22} className="transition-colors" />}
{item.icon && <BaseIcon path={item.icon} size={item.isHeader ? 16 : 22} className="transition-colors" />}
<span
className={`px-2 transition-colors w-40 grow ${
className={`px-2 transition-colors ${
item.isDesktopNoLabel && item.icon ? 'lg:hidden' : ''
}`}
>
@ -120,7 +123,7 @@ export default function NavBarItem({ item }: Props) {
return <BaseDivider navBar />
}
if (item.href) {
if (item.href && !item.isHeader) {
return (
<Link href={item.href} target={item.target} className={componentClass}>
{NavBarItemComponentContents}

View File

@ -1,19 +1,29 @@
import React from 'react'
import { MenuNavBarItem } from '../interfaces'
import NavBarItem from './NavBarItem'
import { useAppSelector } from '../stores/hooks'
import { hasPermission } from '../helpers/userPermissions'
type Props = {
menu: MenuNavBarItem[]
}
export default function NavBarMenuList({ menu }: Props) {
const { currentUser } = useAppSelector((state) => state.auth)
if (!currentUser) return null
return (
<>
{menu.map((item, index) => (
{menu.map((item, index) => {
if (!hasPermission(currentUser, item.permissions)) return null
return (
<div key={index}>
<NavBarItem item={item} />
</div>
))}
)
})}
</>
)
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/programs/programsSlice'
import { loadColumns } from "./configureProgramsCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TablePrograms = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { programs, loading, count, refetch } = useAppSelector((state) => state.programs);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={programs || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TablePrograms;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_PROGRAMS')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/programs/programs-edit/?id=${params?.row?.id}`}
pathView={`/programs/programs-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/propagation_types/propagation_typesSlice'
import { loadColumns } from "./configurePropagation_typesCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TablePropagation_types = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { propagation_types, loading, count, refetch } = useAppSelector((state) => state.propagation_types);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={propagation_types || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TablePropagation_types;

View File

@ -0,0 +1,36 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_PROPAGATION_TYPES')
const hasDeletePermission = hasPermission(user, 'DELETE_PROPAGATION_TYPES')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
params={params}
pathEdit={`/propagation_types/propagation_types-edit/?id=${params?.row?.id}`}
pathView={`/propagation_types/propagation_types-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
hasDeletePermission={hasDeletePermission}
deleteHandler={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/retailer_programs/retailer_programsSlice'
import { loadColumns } from "./configureRetailer_programsCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableRetailer_programs = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { retailer_programs, loading, count, refetch } = useAppSelector((state) => state.retailer_programs);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton color="danger" label="Delete selected" icon={mdiDelete} onClick={() => setIsDeleteModalActive(true)} />,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={retailer_programs || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableRetailer_programs;

View File

@ -0,0 +1,30 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_RETAILER_PROGRAMS')
const hasDeletePermission = hasPermission(user, 'DELETE_RETAILER_PROGRAMS')
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', flex: 1 },
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
params={params}
pathEdit={`/retailer_programs/retailer_programs-edit/?id=${params?.row?.id}`}
pathView={`/retailer_programs/retailer_programs-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
hasDeletePermission={hasDeletePermission}
deleteHandler={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/retailers/retailersSlice'
import { loadColumns } from "./configureRetailersCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableRetailers = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { retailers, loading, count, refetch } = useAppSelector((state) => state.retailers);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={retailers || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableRetailers;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_RETAILERS')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/retailers/retailers-edit/?id=${params?.row?.id}`}
pathView={`/retailers/retailers-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { fetch, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/sites/sitesSlice'
import { loadColumns } from "./configureSitesCols";
import { DataGrid, GridRowSelectionModel } from '@mui/x-data-grid';
import BaseButton from '../BaseButton';
import { mdiDelete } from '@mdi/js';
import CardBoxModal from '../CardBoxModal';
import { createPortal } from 'react-dom';
const TableSites = ({ filterItems, setFilterItems, filters, showGrid }) => {
const dispatch = useAppDispatch();
const { sites, loading, count, refetch } = useAppSelector((state) => state.sites);
const { currentUser } = useAppSelector((state) => state.auth);
const [paginationModel, setPaginationModel] = useState({ pageSize: 10, page: 0 });
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
const [isDeleteModalActive, setIsDeleteModalActive] = useState(false);
useEffect(() => {
const query = `?limit=${paginationModel.pageSize}&page=${paginationModel.page}`;
dispatch(fetch({ query }));
if (refetch) dispatch(setRefetch(false));
}, [dispatch, paginationModel, refetch]);
const deleteHandler = (id: string) => {
dispatch(deleteItem(id)).then(() => dispatch(setRefetch(true)));
};
const handleDeleteByIds = () => {
dispatch(deleteItemsByIds(selectionModel)).then(() => {
dispatch(setRefetch(true));
setIsDeleteModalActive(false);
});
};
const columns = loadColumns(currentUser, deleteHandler);
return (
<div style={{ height: 400, width: '100%' }}>
{selectionModel.length > 0 && typeof document !== 'undefined' && document.getElementById('delete-rows-button') &&
createPortal(
<BaseButton
color="danger"
label="Delete selected"
icon={mdiDelete}
onClick={() => setIsDeleteModalActive(true)}
/>,
document.getElementById('delete-rows-button') as HTMLElement
)
}
<DataGrid
rows={sites || []}
columns={columns}
rowCount={count}
loading={loading}
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
checkboxSelection
onRowSelectionModelChange={(newSelectionModel) => setSelectionModel(newSelectionModel)}
rowSelectionModel={selectionModel}
/>
<CardBoxModal
title="Delete items"
buttonColor="danger"
buttonLabel="Confirm"
isActive={isDeleteModalActive}
onConfirm={handleDeleteByIds}
onCancel={() => setIsDeleteModalActive(false)}
>
<p>Are you sure you want to delete {selectionModel.length} items?</p>
</CardBoxModal>
</div>
);
};
export default TableSites;

View File

@ -0,0 +1,33 @@
import { GridColDef } from '@mui/x-data-grid'
import React from 'react'
import { hasPermission } from "../../helpers/userPermissions";
import ListActionsPopover from "../ListActionsPopover";
export const loadColumns = (user: any, deleteHandler: any) => {
const hasUpdatePermission = hasPermission(user, 'UPDATE_SITES')
const columns: GridColDef[] = [
{
field: 'name',
headerName: 'Name',
flex: 1,
},
{
field: 'actions',
headerName: 'Actions',
sortable: false,
width: 100,
renderCell: (params) => (
<ListActionsPopover
itemId={params?.row?.id}
pathEdit={`/sites/sites-edit/?id=${params?.row?.id}`}
pathView={`/sites/sites-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
onDelete={deleteHandler}
/>
),
},
]
return columns
}

View File

@ -11,6 +11,7 @@ import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
GridToolbar,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureTrialsCols";
import _ from 'lodash';
@ -233,6 +234,9 @@ const TableSampleTrials = ({ filterItems, setFilterItems, filters, showGrid }) =
},
},
}}
slots={{
toolbar: GridToolbar,
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);

View File

@ -1,18 +1,11 @@
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;
@ -20,14 +13,10 @@ 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;
@ -38,327 +27,146 @@ export const loadColumns = async (
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_TRIALS')
const weeksOptions = Array.from({ length: 53 }, (_, i) => ({ id: i + 1, label: `Week ${i + 1}` }));
return [
{
field: 'tenant',
headerName: 'Tenant',
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('tenants'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'project',
headerName: 'Project',
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('projects'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'name',
headerName: 'TrialName',
headerName: 'Trial Name',
flex: 1,
minWidth: 120,
filterable: false,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'variety_name',
headerName: 'VarietyName',
field: 'trial_code',
headerName: 'Trial Code',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'breeder',
headerName: 'Breeder',
flex: 1,
minWidth: 120,
filterable: false,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('breeders'),
valueGetter: (params: GridValueGetterParams) => params?.row?.breeder?.name || params?.row?.breeder?.id || params?.value?.id || params?.value,
},
{
field: 'batch_code',
headerName: 'BatchCode',
field: 'variety_name',
headerName: 'Variety Name',
flex: 1,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'container',
headerName: 'Container',
flex: 1,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('containers'),
valueGetter: (params: GridValueGetterParams) => params?.row?.container?.name || params?.row?.container?.id || params?.value?.id || params?.value,
},
{
field: 'quantity',
headerName: 'Quantity',
flex: 1,
minWidth: 100,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'site',
headerName: 'Site',
flex: 1,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('sites'),
valueGetter: (params: GridValueGetterParams) => params?.row?.site?.name || params?.row?.site?.id || params?.value?.id || params?.value,
},
{
field: 'location',
headerName: 'Location',
flex: 1,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('locations'),
valueGetter: (params: GridValueGetterParams) => params?.row?.location?.name || params?.row?.location?.id || params?.value?.id || params?.value,
},
{
field: 'receiveWeek',
headerName: 'Receive Week',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: weeksOptions,
valueFormatter: (params) => params.value ? `Week ${params.value}` : '',
},
{
field: 'trial_type',
headerName: 'TrialType',
field: 'finishWeek',
headerName: 'Finish Week',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: weeksOptions,
valueFormatter: (params) => params.value ? `Week ${params.value}` : '',
},
{
field: 'status',
headerName: 'Status',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'planted_at',
headerName: 'PlantedAt',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.planted_at),
},
{
field: 'harvested_at',
headerName: 'HarvestedAt',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.harvested_at),
},
{
field: 'greenhouse',
headerName: 'Greenhouse',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'zone',
headerName: 'Zone',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'bench',
headerName: 'Bench',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'plants_count',
headerName: 'PlantsCount',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'target_temperature_c',
headerName: 'TargetTemperature(C)',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'target_humidity_percent',
headerName: 'TargetHumidity(%)',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'target_ec',
headerName: 'TargetEC',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'target_ph',
headerName: 'TargetpH',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'notes',
headerName: 'Notes',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'actions',
type: 'actions',
minWidth: 30,
minWidth: 80,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
@ -366,9 +174,7 @@ export const loadColumns = async (
itemId={params?.row?.id}
pathEdit={`/trials/trials-edit/?id=${params?.row?.id}`}
pathView={`/trials/trials-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]

View File

@ -11,6 +11,7 @@ export type MenuAsideItem = {
target?: string
color?: ColorButtonKey
isLogout?: boolean
isHeader?: boolean
withDevider?: boolean;
menu?: MenuAsideItem[]
permissions?: string | string[]
@ -22,11 +23,13 @@ export type MenuNavBarItem = {
href?: string
target?: string
isDivider?: boolean
isHeader?: boolean
isLogout?: boolean
isDesktopNoLabel?: boolean
isToggleLightDark?: boolean
isCurrentUser?: boolean
menu?: MenuNavBarItem[]
permissions?: string | string[]
}
export type ColorKey = 'white' | 'light' | 'contrast' | 'success' | 'danger' | 'warning' | 'info'

View File

@ -1,13 +1,10 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
import menuNavBar from '../menuNavBar'
import BaseIcon from '../components/BaseIcon'
import NavBar from '../components/NavBar'
import NavBarItemPlain from '../components/NavBarItemPlain'
import AsideMenu from '../components/AsideMenu'
import NavBarMenuList from '../components/NavBarMenuList'
import FooterBar from '../components/FooterBar'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Search from '../components/Search';
@ -67,60 +64,33 @@ export default function LayoutAuthenticated({
const darkMode = useAppSelector((state) => state.style.darkMode)
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
useEffect(() => {
const handleRouteChangeStart = () => {
setIsAsideMobileExpanded(false)
setIsAsideLgActive(false)
// Logic for route change start
}
router.events.on('routeChangeStart', handleRouteChangeStart)
// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
}
}, [router.events, dispatch])
const layoutAsidePadding = 'xl:pl-60'
return (
<div className={`${darkMode ? 'dark' : ''} overflow-hidden lg:overflow-visible`}>
<div
className={`${layoutAsidePadding} ${
isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''
} pt-14 min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100`}
className={`pt-14 min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100`}
>
<NavBar
menu={menuNavBar}
className={`${layoutAsidePadding} ${isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''}`}
leftMenu={menuAside}
className={``}
>
<NavBarItemPlain
display="flex lg:hidden"
onClick={() => setIsAsideMobileExpanded(!isAsideMobileExpanded)}
>
<BaseIcon path={isAsideMobileExpanded ? mdiBackburger : mdiForwardburger} size="24" />
</NavBarItemPlain>
<NavBarItemPlain
display="hidden lg:flex xl:hidden"
onClick={() => setIsAsideLgActive(true)}
>
<BaseIcon path={mdiMenu} size="24" />
</NavBarItemPlain>
<NavBarItemPlain useMargin>
<Search />
</NavBarItemPlain>
</NavBar>
<AsideMenu
isAsideMobileExpanded={isAsideMobileExpanded}
isAsideLgActive={isAsideLgActive}
menu={menuAside}
onAsideLgClose={() => setIsAsideLgActive(false)}
/>
{children}
<FooterBar>Hand-crafted & Made with </FooterBar>
</div>

View File

@ -7,61 +7,12 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
{
href: '/users/users-list',
label: 'Users',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiAccountGroup ?? icon.mdiTable,
permissions: 'READ_USERS'
},
{
href: '/roles/roles-list',
label: 'Roles',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
permissions: 'READ_ROLES'
},
{
href: '/permissions/permissions-list',
label: 'Permissions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
permissions: 'READ_PERMISSIONS'
},
{
href: '/organizations/organizations-list',
label: 'Organizations',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ORGANIZATIONS'
},
{
href: '/tenants/tenants-list',
label: 'Tenants',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_TENANTS'
},
{
href: '/impersonation_sessions/impersonation_sessions-list',
label: 'Impersonation sessions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiAccountSwitch' in icon ? icon['mdiAccountSwitch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_IMPERSONATION_SESSIONS'
},
{
href: '/projects/projects-list',
label: 'Projects',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiBriefcaseOutline' in icon ? icon['mdiBriefcaseOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
icon: 'mdiBriefcaseOutline' in icon ? icon['mdiBriefcaseOutline' as keyof typeof icon] : icon.mdiTable,
permissions: 'READ_PROJECTS'
},
{
@ -69,64 +20,187 @@ const menuAside: MenuAsideItem[] = [
label: 'Trials',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiSprout' in icon ? icon['mdiSprout' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
icon: 'mdiSprout' in icon ? icon['mdiSprout' as keyof typeof icon] : icon.mdiTable,
permissions: 'READ_TRIALS'
},
{
href: '/tracking_activity_types/tracking_activity_types-list',
label: 'Tracking activity types',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFormatListBulletedType' in icon ? icon['mdiFormatListBulletedType' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_TRACKING_ACTIVITY_TYPES'
},
label: 'Tracking',
icon: icon.mdiTimelineTextOutline,
menu: [
{
href: '/tracking_activities/tracking_activities-list',
label: 'Tracking activities',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiTimelineTextOutline' in icon ? icon['mdiTimelineTextOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
label: 'Activities',
icon: icon.mdiCircleSmall,
permissions: 'READ_TRACKING_ACTIVITIES'
},
{
href: '/dashboards/dashboards-list',
label: 'Dashboards',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiViewDashboardOutline' in icon ? icon['mdiViewDashboardOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_DASHBOARDS'
href: '/tracking_activity_types/tracking_activity_types-list',
label: 'Activity Types',
icon: icon.mdiCircleSmall,
permissions: 'READ_TRACKING_ACTIVITY_TYPES'
}
]
},
{
label: 'Reports',
icon: icon.mdiFileChartOutline,
menu: [
{
href: '/report_definitions/report_definitions-list',
label: 'Report definitions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFileChartOutline' in icon ? icon['mdiFileChartOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
label: 'Definitions',
icon: icon.mdiCircleSmall,
permissions: 'READ_REPORT_DEFINITIONS'
},
{
href: '/report_runs/report_runs-list',
label: 'Report runs',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiProgressDownload' in icon ? icon['mdiProgressDownload' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
label: 'Runs',
icon: icon.mdiCircleSmall,
permissions: 'READ_REPORT_RUNS'
}
]
},
{
label: 'Settings',
icon: icon.mdiCogOutline,
menu: [
{
label: 'Data Sets',
icon: icon.mdiDatabaseOutline,
menu: [
{
label: 'Trials',
isHeader: true,
icon: icon.mdiSprout
},
{
href: '/breeders/breeders-list',
label: 'Breeder',
icon: icon.mdiCircleSmall,
permissions: 'READ_BREEDERS'
},
{
href: '/propagation_types/propagation_types-list',
label: 'Propagation Type',
icon: icon.mdiCircleSmall,
permissions: 'READ_PROPAGATION_TYPES'
},
{
href: '/growth_habits/growth_habits-list',
label: 'Growth Habit',
icon: icon.mdiCircleSmall,
permissions: 'READ_GROWTH_HABITS'
},
{
href: '/crop_types/crop_types-list',
label: 'Crop Type',
icon: icon.mdiCircleSmall,
permissions: 'READ_CROP_TYPES'
},
{
href: '/containers/containers-list',
label: 'Container',
icon: icon.mdiCircleSmall,
permissions: 'READ_CONTAINERS'
},
{
href: '/retailer_programs/retailer_programs-list',
label: 'Retailer Program',
icon: icon.mdiCircleSmall,
permissions: 'READ_RETAILER_PROGRAMS'
},
{
href: '/retailers/retailers-list',
label: 'Retailer',
icon: icon.mdiCircleSmall,
permissions: 'READ_RETAILERS'
},
{
href: '/programs/programs-list',
label: 'Program',
icon: icon.mdiCircleSmall,
permissions: 'READ_PROGRAMS'
},
{
href: '/groups/groups-list',
label: 'Group',
icon: icon.mdiCircleSmall,
permissions: 'READ_GROUPS'
},
{
href: '/sites/sites-list',
label: 'Site',
icon: icon.mdiCircleSmall,
permissions: 'READ_SITES'
},
{
href: '/locations/locations-list',
label: 'Default Location',
icon: icon.mdiCircleSmall,
permissions: 'READ_LOCATIONS'
}
]
}
]
},
{
label: 'System',
icon: icon.mdiCogOutline,
menu: [
{
href: '/users/users-list',
label: 'Users',
icon: icon.mdiCircleSmall,
permissions: 'READ_USERS'
},
{
href: '/roles/roles-list',
label: 'Roles',
icon: icon.mdiCircleSmall,
permissions: 'READ_ROLES'
},
{
href: '/permissions/permissions-list',
label: 'Permissions',
icon: icon.mdiCircleSmall,
permissions: 'READ_PERMISSIONS'
},
{
href: '/organizations/organizations-list',
label: 'Organizations',
icon: icon.mdiCircleSmall,
permissions: 'READ_ORGANIZATIONS'
},
{
href: '/tenants/tenants-list',
label: 'Tenants',
icon: icon.mdiCircleSmall,
permissions: 'READ_TENANTS'
},
{
href: '/impersonation_sessions/impersonation_sessions-list',
label: 'Impersonation Sessions',
icon: icon.mdiCircleSmall,
permissions: 'READ_IMPERSONATION_SESSIONS'
},
{
href: '/tenant_settings/tenant_settings-list',
label: 'Tenant settings',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiCogOutline' in icon ? icon['mdiCogOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
label: 'Tenant Settings',
icon: icon.mdiCircleSmall,
permissions: 'READ_TENANT_SETTINGS'
},
{
href: '/dashboards/dashboards-list',
label: 'Dashboard Layouts',
icon: icon.mdiCircleSmall,
permissions: 'READ_DASHBOARDS'
}
]
},
{
href: '/profile',
label: 'Profile',
icon: icon.mdiAccountCircle,
},
{
href: '/api-docs',
target: '_blank',

View File

@ -0,0 +1,72 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { update, fetch } from '../../stores/breeders/breedersSlice'
const BreedersEdit = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { breeders } = useAppSelector(state => state.breeders);
const [initialValues, setInitialValues] = useState({ name: '' });
useEffect(() => {
if (id) {
dispatch(fetch({ id }));
}
}, [dispatch, id]);
useEffect(() => {
if (breeders && !Array.isArray(breeders)) {
setInitialValues({ name: breeders.name || '' });
}
}, [breeders]);
const handleSubmit = async (values: any) => {
await dispatch(update({ id, data: values }));
router.push('/breeders/breeders-list');
};
return (
<>
<Head>
<title>{getPageTitle('Edit Propagation Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Edit Propagation Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit} enableReinitialize>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/breeders/breeders-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
BreedersEdit.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'UPDATE_PROPAGATION_TYPES'}>{page}</LayoutAuthenticated>
}
export default BreedersEdit

View File

@ -0,0 +1,44 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import TableBreeders from '../../components/Breeders/TableBreeders'
import BaseButton from '../../components/BaseButton'
import { useAppSelector } from "../../stores/hooks";
import { hasPermission } from "../../helpers/userPermissions";
const BreedersListPage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_PROPAGATION_TYPES');
return (
<>
<Head>
<title>{getPageTitle('Propagation Types')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Propagation Types" main>
{''}
</SectionTitleLineWithButton>
<div className="mb-6 flex flex-wrap gap-3">
{hasCreatePermission && <BaseButton href={'/breeders/breeders-new'} color='info' label='New Item'/>}
<div id='delete-rows-button'></div>
</div>
<TableBreeders filterItems={[]} setFilterItems={() => undefined} filters={[]} showGrid={false} />
</SectionMain>
</>
)
}
BreedersListPage.getLayout = function getLayout(page: ReactElement) {
return (
<LayoutAuthenticated permission={'READ_PROPAGATION_TYPES'}>
{page}
</LayoutAuthenticated>
)
}
export default BreedersListPage

View File

@ -0,0 +1,59 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch } from "../../stores/hooks"
import { create } from '../../stores/breeders/breedersSlice'
const BreedersNew = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const initialValues = { name: '' };
const handleSubmit = async (values: any) => {
await dispatch(create(values));
router.push('/breeders/breeders-list');
};
return (
<>
<Head>
<title>{getPageTitle('New Propagation Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Propagation Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/breeders/breeders-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
BreedersNew.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'CREATE_PROPAGATION_TYPES'}>{page}</LayoutAuthenticated>
}
export default BreedersNew

View File

@ -0,0 +1,52 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect } from 'react'
import { useRouter } from 'next/router'
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 BaseButton from '../../components/BaseButton'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { fetch } from '../../stores/breeders/breedersSlice'
const BreedersView = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { breeders } = useAppSelector(state => state.breeders);
useEffect(() => {
if (id) {
dispatch(fetch({ id }));
}
}, [dispatch, id]);
return (
<>
<Head>
<title>{getPageTitle('View Propagation Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="View Propagation Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<div className="mb-4">
<label className="font-bold">Name:</label>
<p>{breeders?.name || 'N/A'}</p>
</div>
<BaseButton type="button" color="info" outline label="Back" onClick={() => router.push('/breeders/breeders-list')} />
</CardBox>
</SectionMain>
</>
)
}
BreedersView.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'READ_PROPAGATION_TYPES'}>{page}</LayoutAuthenticated>
}
export default BreedersView

View File

@ -0,0 +1,72 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { update, fetch } from '../../stores/containers/containersSlice'
const ContainersEdit = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { containers } = useAppSelector(state => state.containers);
const [initialValues, setInitialValues] = useState({ name: '' });
useEffect(() => {
if (id) {
dispatch(fetch({ id }));
}
}, [dispatch, id]);
useEffect(() => {
if (containers && !Array.isArray(containers)) {
setInitialValues({ name: containers.name || '' });
}
}, [containers]);
const handleSubmit = async (values: any) => {
await dispatch(update({ id, data: values }));
router.push('/containers/containers-list');
};
return (
<>
<Head>
<title>{getPageTitle('Edit Container')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Edit Container" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit} enableReinitialize>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/containers/containers-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
ContainersEdit.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'UPDATE_CONTAINERS'}>{page}</LayoutAuthenticated>
}
export default ContainersEdit

View File

@ -0,0 +1,44 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import TableContainers from '../../components/Containers/TableContainers'
import BaseButton from '../../components/BaseButton'
import { useAppSelector } from "../../stores/hooks";
import { hasPermission } from "../../helpers/userPermissions";
const ContainersListPage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CONTAINERS');
return (
<>
<Head>
<title>{getPageTitle('Containers')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Containers" main>
{''}
</SectionTitleLineWithButton>
<div className="mb-6 flex flex-wrap gap-3">
{hasCreatePermission && <BaseButton href={'/containers/containers-new'} color='info' label='New Item'/>}
<div id='delete-rows-button'></div>
</div>
<TableContainers filterItems={[]} setFilterItems={() => undefined} filters={[]} showGrid={false} />
</SectionMain>
</>
)
}
ContainersListPage.getLayout = function getLayout(page: ReactElement) {
return (
<LayoutAuthenticated permission={'READ_CONTAINERS'}>
{page}
</LayoutAuthenticated>
)
}
export default ContainersListPage

View File

@ -0,0 +1,59 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch } from "../../stores/hooks"
import { create } from '../../stores/containers/containersSlice'
const ContainersNew = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const initialValues = { name: '' };
const handleSubmit = async (values: any) => {
await dispatch(create(values));
router.push('/containers/containers-list');
};
return (
<>
<Head>
<title>{getPageTitle('New Container')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Container" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/containers/containers-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
ContainersNew.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'CREATE_CONTAINERS'}>{page}</LayoutAuthenticated>
}
export default ContainersNew

View File

@ -0,0 +1,37 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect } from 'react'
import { useRouter } from 'next/router'
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 BaseButton from '../../components/BaseButton'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { fetch } from '../../stores/containers/containersSlice'
const ContainersView = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { containers } = useAppSelector(state => state.containers);
useEffect(() => { if (id) dispatch(fetch({ id })); }, [dispatch, id]);
return (
<>
<Head><title>{getPageTitle('View Container')}</title></Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="View Container" main>{''}</SectionTitleLineWithButton>
<CardBox>
<div className="mb-4"><label className="font-bold">Name:</label><p>{containers?.name || 'N/A'}</p></div>
<BaseButton type="button" color="info" outline label="Back" onClick={() => router.push('/containers/containers-list')} />
</CardBox>
</SectionMain>
</>
)
}
ContainersView.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'READ_CONTAINERS'}>{page}</LayoutAuthenticated>
}
export default ContainersView

View File

@ -0,0 +1,72 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { update, fetch } from '../../stores/crop_types/crop_typesSlice'
const Crop_typesEdit = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { crop_types } = useAppSelector(state => state.crop_types);
const [initialValues, setInitialValues] = useState({ name: '' });
useEffect(() => {
if (id) {
dispatch(fetch({ id }));
}
}, [dispatch, id]);
useEffect(() => {
if (crop_types && !Array.isArray(crop_types)) {
setInitialValues({ name: crop_types.name || '' });
}
}, [crop_types]);
const handleSubmit = async (values: any) => {
await dispatch(update({ id, data: values }));
router.push('/crop_types/crop_types-list');
};
return (
<>
<Head>
<title>{getPageTitle('Edit Crop Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Edit Crop Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit} enableReinitialize>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/crop_types/crop_types-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
Crop_typesEdit.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'UPDATE_CROP_TYPES'}>{page}</LayoutAuthenticated>
}
export default Crop_typesEdit

View File

@ -0,0 +1,44 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import TableCrop_types from '../../components/Crop_types/TableCrop_types'
import BaseButton from '../../components/BaseButton'
import { useAppSelector } from "../../stores/hooks";
import { hasPermission } from "../../helpers/userPermissions";
const Crop_typesListPage = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CROP_TYPES');
return (
<>
<Head>
<title>{getPageTitle('Crop Types')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Crop Types" main>
{''}
</SectionTitleLineWithButton>
<div className="mb-6 flex flex-wrap gap-3">
{hasCreatePermission && <BaseButton href={'/crop_types/crop_types-new'} color='info' label='New Item'/>}
<div id='delete-rows-button'></div>
</div>
<TableCrop_types filterItems={[]} setFilterItems={() => undefined} filters={[]} showGrid={false} />
</SectionMain>
</>
)
}
Crop_typesListPage.getLayout = function getLayout(page: ReactElement) {
return (
<LayoutAuthenticated permission={'READ_CROP_TYPES'}>
{page}
</LayoutAuthenticated>
)
}
export default Crop_typesListPage

View File

@ -0,0 +1,59 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch } from "../../stores/hooks"
import { create } from '../../stores/crop_types/crop_typesSlice'
const Crop_typesNew = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const initialValues = { name: '' };
const handleSubmit = async (values: any) => {
await dispatch(create(values));
router.push('/crop_types/crop_types-list');
};
return (
<>
<Head>
<title>{getPageTitle('New Crop Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Crop Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/crop_types/crop_types-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
Crop_typesNew.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'CREATE_CROP_TYPES'}>{page}</LayoutAuthenticated>
}
export default Crop_typesNew

View File

@ -0,0 +1,37 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect } from 'react'
import { useRouter } from 'next/router'
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 BaseButton from '../../components/BaseButton'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { fetch } from '../../stores/crop_types/crop_typesSlice'
const Crop_typesView = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { crop_types } = useAppSelector(state => state.crop_types);
useEffect(() => { if (id) dispatch(fetch({ id })); }, [dispatch, id]);
return (
<>
<Head><title>{getPageTitle('View Crop Type')}</title></Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="View Crop Type" main>{''}</SectionTitleLineWithButton>
<CardBox>
<div className="mb-4"><label className="font-bold">Name:</label><p>{crop_types?.name || 'N/A'}</p></div>
<BaseButton type="button" color="info" outline label="Back" onClick={() => router.push('/crop_types/crop_types-list')} />
</CardBox>
</SectionMain>
</>
)
}
Crop_typesView.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'READ_CROP_TYPES'}>{page}</LayoutAuthenticated>
}
export default Crop_typesView

View File

@ -9,13 +9,18 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton
import BaseIcon from "../components/BaseIcon";
import { getPageTitle } from '../config'
import Link from "next/link";
import moment from 'moment';
import { hasPermission } from "../helpers/userPermissions";
import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { fetch as fetchActivities } from '../stores/tracking_activities/tracking_activitiesSlice';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import CardBox from '../components/CardBox';
import UserAvatar from '../components/UserAvatar';
const Dashboard = () => {
const dispatch = useAppDispatch();
const iconsColor = useAppSelector((state) => state.style.iconsColor);
@ -24,526 +29,206 @@ const Dashboard = () => {
const loadingMessage = 'Loading...';
const [users, setUsers] = React.useState(loadingMessage);
const [roles, setRoles] = React.useState(loadingMessage);
const [permissions, setPermissions] = React.useState(loadingMessage);
const [organizations, setOrganizations] = React.useState(loadingMessage);
const [tenants, setTenants] = React.useState(loadingMessage);
const [impersonation_sessions, setImpersonation_sessions] = React.useState(loadingMessage);
const [projects, setProjects] = React.useState(loadingMessage);
const [trials, setTrials] = React.useState(loadingMessage);
const [tracking_activity_types, setTracking_activity_types] = React.useState(loadingMessage);
const [tracking_activities, setTracking_activities] = React.useState(loadingMessage);
const [dashboards, setDashboards] = React.useState(loadingMessage);
const [report_definitions, setReport_definitions] = React.useState(loadingMessage);
const [report_runs, setReport_runs] = React.useState(loadingMessage);
const [tenant_settings, setTenant_settings] = React.useState(loadingMessage);
const [projectsCount, setProjectsCount] = React.useState(loadingMessage);
const [trialsCount, setTrialsCount] = React.useState(loadingMessage);
const [activitiesCount, setActivitiesCount] = React.useState(loadingMessage);
const [widgetsRole, setWidgetsRole] = React.useState({
role: { value: '', label: '' },
});
const { currentUser } = useAppSelector((state) => state.auth);
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
const { rolesWidgets, loading: widgetsLoading } = useAppSelector((state) => state.roles);
const { tracking_activities, loading: activitiesLoading } = useAppSelector((state) => state.tracking_activities);
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
const organizationId = currentUser?.organizations?.id;
async function loadData() {
const entities = ['users','roles','permissions','organizations','tenants','impersonation_sessions','projects','trials','tracking_activity_types','tracking_activities','dashboards','report_definitions','report_runs','tenant_settings',];
const fns = [setUsers,setRoles,setPermissions,setOrganizations,setTenants,setImpersonation_sessions,setProjects,setTrials,setTracking_activity_types,setTracking_activities,setDashboards,setReport_definitions,setReport_runs,setTenant_settings,];
async function loadCounts() {
const entities = ['users', 'projects', 'trials', 'tracking_activities'];
const setters = [setUsers, setProjectsCount, setTrialsCount, setActivitiesCount];
const requests = entities.map((entity, index) => {
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
return axios.get(`/${entity.toLowerCase()}/count`);
} else {
fns[index](null);
return Promise.resolve({data: {count: null}});
setters[index](null);
return Promise.resolve({ data: { count: null } });
}
});
Promise.allSettled(requests).then((results) => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
fns[i](result.value.data.count);
setters[i](result.value.data.count);
} else {
fns[i](result.reason.message);
setters[i](result.reason.message);
}
});
});
}
async function getWidgets(roleId) {
await dispatch(fetchWidgets(roleId));
}
React.useEffect(() => {
if (!currentUser) return;
loadData().then();
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
}, [currentUser]);
loadCounts().then();
if (hasPermission(currentUser, 'READ_TRACKING_ACTIVITIES')) {
dispatch(fetchActivities({ query: '?limit=10' }));
}
dispatch(fetchWidgets(currentUser?.app_role?.id || '')).then();
}, [currentUser, dispatch]);
React.useEffect(() => {
if (!currentUser || !widgetsRole?.role?.value) return;
getWidgets(widgetsRole?.role?.value || '').then();
}, [widgetsRole?.role?.value]);
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'critical': return 'text-red-600';
case 'high': return 'text-orange-600';
case 'medium': return 'text-yellow-600';
case 'low': return 'text-blue-600';
default: return 'text-gray-600';
}
};
return (
<>
<Head>
<title>
{getPageTitle('Overview')}
</title>
<title>{getPageTitle('Dashboard')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={icon.mdiChartTimelineVariant}
title='Overview'
main>
icon={icon.mdiViewDashboardOutline}
title='Trial Tracker Dashboard'
main
>
{''}
</SectionTitleLineWithButton>
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
{/* Quick Stats */}
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6'>
{hasPermission(currentUser, 'READ_PROJECTS') && (
<CardBox className={cardsStyle}>
<div className="flex justify-between items-center">
<div>
<p className="text-gray-500 dark:text-gray-400">Projects</p>
<h3 className="text-2xl font-bold">{projectsCount}</h3>
</div>
<BaseIcon path={icon.mdiBriefcaseOutline} size={32} className={iconsColor} />
</div>
</CardBox>
)}
{hasPermission(currentUser, 'READ_TRIALS') && (
<CardBox className={cardsStyle}>
<div className="flex justify-between items-center">
<div>
<p className="text-gray-500 dark:text-gray-400">Active Trials</p>
<h3 className="text-2xl font-bold">{trialsCount}</h3>
</div>
<BaseIcon path={icon.mdiSprout} size={32} className={iconsColor} />
</div>
</CardBox>
)}
{hasPermission(currentUser, 'READ_TRACKING_ACTIVITIES') && (
<CardBox className={cardsStyle}>
<div className="flex justify-between items-center">
<div>
<p className="text-gray-500 dark:text-gray-400">Total Activities</p>
<h3 className="text-2xl font-bold">{activitiesCount}</h3>
</div>
<BaseIcon path={icon.mdiTimelineTextOutline} size={32} className={iconsColor} />
</div>
</CardBox>
)}
{hasPermission(currentUser, 'READ_USERS') && (
<CardBox className={cardsStyle}>
<div className="flex justify-between items-center">
<div>
<p className="text-gray-500 dark:text-gray-400">Team Members</p>
<h3 className="text-2xl font-bold">{users}</h3>
</div>
<BaseIcon path={icon.mdiAccountGroup} size={32} className={iconsColor} />
</div>
</CardBox>
)}
</div>
{hasPermission(currentUser, 'CREATE_ROLES') && (
<WidgetCreator
currentUser={currentUser}
isFetchingQuery={isFetchingQuery}
setWidgetsRole={setWidgetsRole}
widgetsRole={widgetsRole}
/>}
{!!rolesWidgets.length &&
hasPermission(currentUser, 'CREATE_ROLES') && (
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
setWidgetsRole={() => {
// No-op for dashboard feed view
}}
widgetsRole={{ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }}
/>
)}
<div className='grid grid-cols-1 gap-6 lg:grid-cols-3'>
{/* Activity Feed */}
<div className="lg:col-span-2 space-y-4">
<SectionTitleLineWithButton icon={icon.mdiHistory} title="Recent Activities" />
{activitiesLoading ? (
<CardBox className="flex items-center justify-center p-12">
<BaseIcon path={icon.mdiLoading} size={32} className="animate-spin" />
<span className="ml-3">Loading activities...</span>
</CardBox>
) : tracking_activities && tracking_activities.length > 0 ? (
tracking_activities.map((activity: any) => (
<CardBox key={activity.id} className={`${cardsStyle} hover:shadow-md transition-shadow`}>
<div className="flex items-start space-x-4">
<UserAvatar username={activity.recorded_by?.firstName || 'Staff'} className="w-10 h-10" />
<div className="flex-1">
<div className="flex justify-between items-start">
<div>
<span className="font-bold">{activity.recorded_by?.firstName} {activity.recorded_by?.lastName}</span>
<span className="text-gray-500 ml-2">logged an activity for</span>
<Link href={`/trials/trials-edit?id=${activity.trial?.id}`} className="ml-1 text-green-600 hover:underline font-semibold">
{activity.trial?.name}
</Link>
</div>
<span className="text-xs text-gray-400">
{moment(activity.occurred_at || activity.createdAt).fromNow()}
</span>
</div>
<div className="mt-2">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded">
{activity.activity_type?.name}
</span>
{activity.severity && activity.severity !== 'none' && (
<span className={`text-xs font-bold uppercase ${getSeverityColor(activity.severity)}`}>
{activity.severity}
</span>
)}
</div>
<p className="mt-2 text-gray-700 dark:text-gray-300 font-medium">
{activity.summary}
</p>
{activity.details && (
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
{activity.details}
</p>
)}
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
{(isFetchingQuery || loading) && (
<div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
<BaseIcon
className={`${iconsColor} animate-spin mr-5`}
w='w-16'
h='h-16'
size={48}
path={icon.mdiLoading}
/>{' '}
Loading widgets...
</div>
</div>
</div>
</CardBox>
))
) : (
<CardBox className="p-12 text-center text-gray-500">
No recent activities found. Start tracking to see them here!
</CardBox>
)}
</div>
{ rolesWidgets &&
rolesWidgets.map((widget) => (
{/* Right Column: Widgets or Quick Info */}
<div className="space-y-6">
<SectionTitleLineWithButton icon={icon.mdiChartPie} title="Distribution" />
<div className='grid grid-cols-1 gap-6'>
{rolesWidgets && rolesWidgets.map((widget) => (
<SmartWidget
key={widget.id}
userId={currentUser?.id}
widget={widget}
roleId={widgetsRole?.role?.value || ''}
roleId={currentUser?.app_role?.id || ''}
admin={hasPermission(currentUser, 'CREATE_ROLES')}
/>
))}
</div>
{!!rolesWidgets.length && <hr className='my-6 ' />}
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Users
</div>
<div className="text-3xl leading-tight font-semibold">
{users}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiAccountGroup || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Roles
</div>
<div className="text-3xl leading-tight font-semibold">
{roles}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Permissions
</div>
<div className="text-3xl leading-tight font-semibold">
{permissions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ORGANIZATIONS') && <Link href={'/organizations/organizations-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Organizations
</div>
<div className="text-3xl leading-tight font-semibold">
{organizations}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TENANTS') && <Link href={'/tenants/tenants-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tenants
</div>
<div className="text-3xl leading-tight font-semibold">
{tenants}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_IMPERSONATION_SESSIONS') && <Link href={'/impersonation_sessions/impersonation_sessions-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Impersonation sessions
</div>
<div className="text-3xl leading-tight font-semibold">
{impersonation_sessions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiAccountSwitch' in icon ? icon['mdiAccountSwitch' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PROJECTS') && <Link href={'/projects/projects-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Projects
</div>
<div className="text-3xl leading-tight font-semibold">
{projects}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiBriefcaseOutline' in icon ? icon['mdiBriefcaseOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TRIALS') && <Link href={'/trials/trials-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Trials
</div>
<div className="text-3xl leading-tight font-semibold">
{trials}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiSprout' in icon ? icon['mdiSprout' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TRACKING_ACTIVITY_TYPES') && <Link href={'/tracking_activity_types/tracking_activity_types-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tracking activity types
</div>
<div className="text-3xl leading-tight font-semibold">
{tracking_activity_types}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFormatListBulletedType' in icon ? icon['mdiFormatListBulletedType' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TRACKING_ACTIVITIES') && <Link href={'/tracking_activities/tracking_activities-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tracking activities
</div>
<div className="text-3xl leading-tight font-semibold">
{tracking_activities}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiTimelineTextOutline' in icon ? icon['mdiTimelineTextOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_DASHBOARDS') && <Link href={'/dashboards/dashboards-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Dashboards
</div>
<div className="text-3xl leading-tight font-semibold">
{dashboards}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiViewDashboardOutline' in icon ? icon['mdiViewDashboardOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_REPORT_DEFINITIONS') && <Link href={'/report_definitions/report_definitions-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Report definitions
</div>
<div className="text-3xl leading-tight font-semibold">
{report_definitions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFileChartOutline' in icon ? icon['mdiFileChartOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_REPORT_RUNS') && <Link href={'/report_runs/report_runs-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Report runs
</div>
<div className="text-3xl leading-tight font-semibold">
{report_runs}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiProgressDownload' in icon ? icon['mdiProgressDownload' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TENANT_SETTINGS') && <Link href={'/tenant_settings/tenant_settings-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tenant settings
</div>
<div className="text-3xl leading-tight font-semibold">
{tenant_settings}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiCogOutline' in icon ? icon['mdiCogOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
</div>
</SectionMain>
</>

View File

@ -0,0 +1,72 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { Formik, Form, Field } from 'formik'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import FormField from '../../components/FormField'
import BaseButton from '../../components/BaseButton'
import BaseButtons from '../../components/BaseButtons'
import { useAppDispatch, useAppSelector } from "../../stores/hooks"
import { update, fetch } from '../../stores/groups/groupsSlice'
const GroupsEdit = () => {
const dispatch = useAppDispatch();
const router = useRouter();
const { id } = router.query;
const { groups } = useAppSelector(state => state.groups);
const [initialValues, setInitialValues] = useState({ name: '' });
useEffect(() => {
if (id) {
dispatch(fetch({ id }));
}
}, [dispatch, id]);
useEffect(() => {
if (groups && !Array.isArray(groups)) {
setInitialValues({ name: groups.name || '' });
}
}, [groups]);
const handleSubmit = async (values: any) => {
await dispatch(update({ id, data: values }));
router.push('/groups/groups-list');
};
return (
<>
<Head>
<title>{getPageTitle('Edit Propagation Type')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Edit Propagation Type" main>
{''}
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit} enableReinitialize>
<Form>
<FormField label="Name" help="Please enter the name">
<Field name="name" placeholder="Name" className="w-full h-10 px-3 border rounded-md" required />
</FormField>
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit" />
<BaseButton type="button" color="info" outline label="Cancel" onClick={() => router.push('/groups/groups-list')} />
</BaseButtons>
</Form>
</Formik>
</CardBox>
</SectionMain>
</>
)
}
GroupsEdit.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'UPDATE_PROPAGATION_TYPES'}>{page}</LayoutAuthenticated>
}
export default GroupsEdit

Some files were not shown because too many files have changed in this diff Show More