Autosave: 20260623-140927
This commit is contained in:
parent
27e732ea5f
commit
8156349b96
@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
const FileDBApi = require('./file');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const Utils = require('../utils');
|
const Utils = require('../utils');
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +23,14 @@ module.exports = class SchoolsDBApi {
|
|||||||
||
|
||
|
||||||
null
|
null
|
||||||
,
|
,
|
||||||
|
nif: data.nif || null,
|
||||||
|
phone: data.phone || null,
|
||||||
|
email: data.email || null,
|
||||||
|
province: data.province || null,
|
||||||
|
municipality: data.municipality || null,
|
||||||
|
address: data.address || null,
|
||||||
|
logoUrl: data.logoUrl || null,
|
||||||
|
status: data.status || 'active',
|
||||||
|
|
||||||
importHash: data.importHash || null,
|
importHash: data.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
@ -55,6 +61,14 @@ module.exports = class SchoolsDBApi {
|
|||||||
||
|
||
|
||||||
null
|
null
|
||||||
,
|
,
|
||||||
|
nif: item.nif || null,
|
||||||
|
phone: item.phone || null,
|
||||||
|
email: item.email || null,
|
||||||
|
province: item.province || null,
|
||||||
|
municipality: item.municipality || null,
|
||||||
|
address: item.address || null,
|
||||||
|
logoUrl: item.logoUrl || null,
|
||||||
|
status: item.status || 'active',
|
||||||
|
|
||||||
importHash: item.importHash || null,
|
importHash: item.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
@ -74,8 +88,6 @@ module.exports = class SchoolsDBApi {
|
|||||||
static async update(id, data, options) {
|
static async update(id, data, options) {
|
||||||
const currentUser = (options && options.currentUser) || {id: null};
|
const currentUser = (options && options.currentUser) || {id: null};
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const transaction = (options && options.transaction) || undefined;
|
||||||
const globalAccess = currentUser.app_role?.globalAccess;
|
|
||||||
|
|
||||||
const schools = await db.schools.findByPk(id, {}, {transaction});
|
const schools = await db.schools.findByPk(id, {}, {transaction});
|
||||||
|
|
||||||
|
|
||||||
@ -84,6 +96,14 @@ module.exports = class SchoolsDBApi {
|
|||||||
const updatePayload = {};
|
const updatePayload = {};
|
||||||
|
|
||||||
if (data.name !== undefined) updatePayload.name = data.name;
|
if (data.name !== undefined) updatePayload.name = data.name;
|
||||||
|
if (data.nif !== undefined) updatePayload.nif = data.nif;
|
||||||
|
if (data.phone !== undefined) updatePayload.phone = data.phone;
|
||||||
|
if (data.email !== undefined) updatePayload.email = data.email;
|
||||||
|
if (data.province !== undefined) updatePayload.province = data.province;
|
||||||
|
if (data.municipality !== undefined) updatePayload.municipality = data.municipality;
|
||||||
|
if (data.address !== undefined) updatePayload.address = data.address;
|
||||||
|
if (data.logoUrl !== undefined) updatePayload.logoUrl = data.logoUrl;
|
||||||
|
if (data.status !== undefined) updatePayload.status = data.status;
|
||||||
|
|
||||||
|
|
||||||
updatePayload.updatedById = currentUser.id;
|
updatePayload.updatedById = currentUser.id;
|
||||||
@ -286,10 +306,6 @@ module.exports = class SchoolsDBApi {
|
|||||||
|
|
||||||
offset = currentPage * limit;
|
offset = currentPage * limit;
|
||||||
|
|
||||||
const orderBy = null;
|
|
||||||
|
|
||||||
const transaction = (options && options.transaction) || undefined;
|
|
||||||
|
|
||||||
let include = [
|
let include = [
|
||||||
|
|
||||||
|
|
||||||
@ -316,6 +332,24 @@ module.exports = class SchoolsDBApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter.status) {
|
||||||
|
where = {
|
||||||
|
...where,
|
||||||
|
status: filter.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.province) {
|
||||||
|
where = {
|
||||||
|
...where,
|
||||||
|
[Op.and]: Utils.ilike(
|
||||||
|
'schools',
|
||||||
|
'province',
|
||||||
|
filter.province,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
const transaction = await queryInterface.sequelize.transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryInterface.addColumn('schools', 'nif', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'phone', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'email', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'province', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'municipality', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'address', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn('schools', 'logoUrl', { type: Sequelize.DataTypes.TEXT }, { transaction });
|
||||||
|
await queryInterface.addColumn(
|
||||||
|
'schools',
|
||||||
|
'status',
|
||||||
|
{
|
||||||
|
type: Sequelize.DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'active',
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
|
||||||
|
await transaction.commit();
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface) {
|
||||||
|
const transaction = await queryInterface.sequelize.transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryInterface.removeColumn('schools', 'status', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'logoUrl', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'address', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'municipality', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'province', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'email', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'phone', { transaction });
|
||||||
|
await queryInterface.removeColumn('schools', 'nif', { transaction });
|
||||||
|
|
||||||
|
await transaction.commit();
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,9 +1,3 @@
|
|||||||
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) {
|
module.exports = function(sequelize, DataTypes) {
|
||||||
const schools = sequelize.define(
|
const schools = sequelize.define(
|
||||||
'schools',
|
'schools',
|
||||||
@ -19,6 +13,63 @@ name: {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
nif: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
phone: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
email: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
province: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
municipality: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
address: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
logoUrl: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
status: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'active',
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
importHash: {
|
importHash: {
|
||||||
|
|||||||
@ -31,9 +31,22 @@ router.use(checkCrudPermissions('schools'));
|
|||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
* default: name
|
* default: name
|
||||||
|
* nif:
|
||||||
|
* type: string
|
||||||
|
* phone:
|
||||||
|
* type: string
|
||||||
|
* email:
|
||||||
|
* type: string
|
||||||
|
* province:
|
||||||
|
* type: string
|
||||||
|
* municipality:
|
||||||
|
* type: string
|
||||||
|
* address:
|
||||||
|
* type: string
|
||||||
|
* logoUrl:
|
||||||
|
* type: string
|
||||||
|
* status:
|
||||||
|
* type: string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -292,11 +305,7 @@ router.get('/', wrapAsync(async (req, res) => {
|
|||||||
req.query, globalAccess, { currentUser }
|
req.query, globalAccess, { currentUser }
|
||||||
);
|
);
|
||||||
if (filetype && filetype === 'csv') {
|
if (filetype && filetype === 'csv') {
|
||||||
const fields = ['id','name',
|
const fields = ['id','name','nif','phone','email','province','municipality','address','logoUrl','status'];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
];
|
|
||||||
const opts = { fields };
|
const opts = { fields };
|
||||||
try {
|
try {
|
||||||
const csv = parse(payload.rows, opts);
|
const csv = parse(payload.rows, opts);
|
||||||
|
|||||||
@ -3,20 +3,50 @@ const SchoolsDBApi = require('../db/api/schools');
|
|||||||
const processFile = require("../middlewares/upload");
|
const processFile = require("../middlewares/upload");
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
const axios = require('axios');
|
|
||||||
const config = require('../config');
|
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
|
||||||
|
const SCHOOL_STATUSES = new Set(['active', 'setup', 'suspended']);
|
||||||
|
|
||||||
|
function cleanString(value) {
|
||||||
|
if (value === undefined || value === null) return null;
|
||||||
|
const text = String(value).trim();
|
||||||
|
return text || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSchoolData(data = {}) {
|
||||||
|
const normalized = {
|
||||||
|
name: cleanString(data.name),
|
||||||
|
nif: cleanString(data.nif),
|
||||||
|
phone: cleanString(data.phone),
|
||||||
|
email: cleanString(data.email),
|
||||||
|
province: cleanString(data.province),
|
||||||
|
municipality: cleanString(data.municipality),
|
||||||
|
address: cleanString(data.address),
|
||||||
|
logoUrl: cleanString(data.logoUrl),
|
||||||
|
status: cleanString(data.status) || 'active',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!normalized.name) {
|
||||||
|
throw new ValidationError('schoolsNameRequired');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized.email && !normalized.email.includes('@')) {
|
||||||
|
throw new ValidationError('schoolsEmailInvalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SCHOOL_STATUSES.has(normalized.status)) {
|
||||||
|
throw new ValidationError('schoolsStatusInvalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = class SchoolsService {
|
module.exports = class SchoolsService {
|
||||||
static async create(data, currentUser) {
|
static async create(data, currentUser) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
try {
|
try {
|
||||||
await SchoolsDBApi.create(
|
await SchoolsDBApi.create(
|
||||||
data,
|
normalizeSchoolData(data),
|
||||||
{
|
{
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
@ -28,9 +58,9 @@ module.exports = class SchoolsService {
|
|||||||
await transaction.rollback();
|
await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
static async bulkImport(req, res, sendInvitationEmails = true, host) {
|
static async bulkImport(req, res) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -81,7 +111,7 @@ module.exports = class SchoolsService {
|
|||||||
|
|
||||||
const updatedSchools = await SchoolsDBApi.update(
|
const updatedSchools = await SchoolsDBApi.update(
|
||||||
id,
|
id,
|
||||||
data,
|
normalizeSchoolData(data),
|
||||||
{
|
{
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
@ -95,7 +125,7 @@ module.exports = class SchoolsService {
|
|||||||
await transaction.rollback();
|
await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(ids, currentUser) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|||||||
@ -4,34 +4,66 @@
|
|||||||
"pageTitle": "Dashboard",
|
"pageTitle": "Dashboard",
|
||||||
"overview": "Overview",
|
"overview": "Overview",
|
||||||
"loadingWidgets": "Loading widgets...",
|
"loadingWidgets": "Loading widgets...",
|
||||||
"loading": "Loading..."
|
"loading": "Loading...",
|
||||||
|
"roleWidgets": "{{role}}'s widgets"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"pageTitle": "Login",
|
"pageTitle": "Login",
|
||||||
|
|
||||||
"form": {
|
"form": {
|
||||||
"loginLabel": "Login",
|
"loginLabel": "Login",
|
||||||
"loginHelp": "Please enter your login",
|
"loginHelp": "Please enter your login",
|
||||||
"passwordLabel": "Password",
|
"passwordLabel": "Password",
|
||||||
"passwordHelp": "Please enter your password",
|
"passwordHelp": "Please enter your password",
|
||||||
"remember": "Remember",
|
"remember": "Remember",
|
||||||
"forgotPassword": "Forgot password?",
|
"forgotPassword": "Forgot password?",
|
||||||
"loginButton": "Login",
|
"loginButton": "Login",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"noAccountYet": "Don’t have an account yet?",
|
"noAccountYet": "Don’t have an account yet?",
|
||||||
"newAccount": "New Account"
|
"newAccount": "New Account"
|
||||||
},
|
},
|
||||||
|
|
||||||
"pexels": {
|
"pexels": {
|
||||||
"photoCredit": "Photo by {{photographer}} on Pexels",
|
"photoCredit": "Photo by {{photographer}} on Pexels",
|
||||||
"videoCredit": "Video by {{name}} on Pexels",
|
"videoCredit": "Video by {{name}} on Pexels",
|
||||||
"videoUnsupported": "Your browser does not support the video tag."
|
"videoUnsupported": "Your browser does not support the video tag."
|
||||||
},
|
},
|
||||||
|
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "© {{year}} {{title}}. All rights reserved",
|
"copyright": "© {{year}} {{title}}. All rights reserved",
|
||||||
"privacy": "Privacy Policy"
|
"privacy": "Privacy Policy"
|
||||||
}
|
},
|
||||||
|
"sampleCredentialsSuperAdmin": "Use to login as Super Admin:",
|
||||||
|
"sampleCredentialsAdmin": "Use to login as Admin:",
|
||||||
|
"sampleCredentialsUser": "Use to login as User:"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailHelp": "Please enter your email",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordHelp": "Please enter your password",
|
||||||
|
"confirmPasswordLabel": "Confirm Password",
|
||||||
|
"confirmPasswordHelp": "Please confirm your password",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"login": "Login",
|
||||||
|
"checkEmailVerification": "Please check your email for verification link",
|
||||||
|
"genericError": "Something was wrong. Try again"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"pageTitle": "Register",
|
||||||
|
"organization": "Organization",
|
||||||
|
"selectOrganization": "Select organization...",
|
||||||
|
"submit": "Register"
|
||||||
|
},
|
||||||
|
"forgot": {
|
||||||
|
"pageTitle": "Forgot password",
|
||||||
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"verifyEmail": {
|
||||||
|
"pageTitle": "Verify Email",
|
||||||
|
"success": "Your email was verified"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"setTitle": "Set Password",
|
||||||
|
"resetTitle": "Reset Password",
|
||||||
|
"enterNewPassword": "Enter your new password"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@ -41,12 +73,114 @@
|
|||||||
"settingsTitle": "Widget Creator Settings",
|
"settingsTitle": "Widget Creator Settings",
|
||||||
"settingsDescription": "What role are we showing and creating widgets for?",
|
"settingsDescription": "What role are we showing and creating widgets for?",
|
||||||
"doneButton": "Done",
|
"doneButton": "Done",
|
||||||
"loading": "Loading..."
|
"loading": "Loading...",
|
||||||
|
"creationError": "Error with widget creation"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search",
|
"placeholder": "Search",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"minLength": "Minimum length: {{count}} characters"
|
"minLength": "Minimum length: {{count}} characters"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"aside": {
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"users": "Users",
|
||||||
|
"roles": "Roles",
|
||||||
|
"permissions": "Permissions",
|
||||||
|
"schoolOnboarding": "School Onboarding",
|
||||||
|
"academicMvp": "Academic MVP",
|
||||||
|
"schoolsCrud": "Schools CRUD",
|
||||||
|
"students": "Students",
|
||||||
|
"guardians": "Guardians",
|
||||||
|
"studentGuardians": "Student guardians",
|
||||||
|
"teachers": "Teachers",
|
||||||
|
"courses": "Courses",
|
||||||
|
"grades": "Grades",
|
||||||
|
"classes": "Classes",
|
||||||
|
"subjects": "Subjects",
|
||||||
|
"enrollments": "Enrollments",
|
||||||
|
"assessments": "Assessments",
|
||||||
|
"attendance": "Attendance",
|
||||||
|
"invoices": "Invoices",
|
||||||
|
"payments": "Payments",
|
||||||
|
"employees": "Employees",
|
||||||
|
"products": "Products",
|
||||||
|
"books": "Books",
|
||||||
|
"bookLoans": "Book loans",
|
||||||
|
"profile": "Profile",
|
||||||
|
"swaggerApi": "Swagger API"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"myProfile": "My Profile",
|
||||||
|
"logOut": "Log Out",
|
||||||
|
"lightDark": "Light/Dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"footer": {
|
||||||
|
"madeWith": "Hand-crafted & Made with ❤️"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entities": {
|
||||||
|
"users": "Users",
|
||||||
|
"roles": "Roles",
|
||||||
|
"permissions": "Permissions",
|
||||||
|
"schools": "Schools",
|
||||||
|
"students": "Students",
|
||||||
|
"guardians": "Guardians",
|
||||||
|
"studentGuardians": "Student guardians",
|
||||||
|
"teachers": "Teachers",
|
||||||
|
"courses": "Courses",
|
||||||
|
"grades": "Grades",
|
||||||
|
"classes": "Classes",
|
||||||
|
"subjects": "Subjects",
|
||||||
|
"enrollments": "Enrollments",
|
||||||
|
"assessments": "Assessments",
|
||||||
|
"attendance": "Attendance",
|
||||||
|
"invoices": "Invoices",
|
||||||
|
"payments": "Payments",
|
||||||
|
"employees": "Employees",
|
||||||
|
"products": "Products",
|
||||||
|
"books": "Books",
|
||||||
|
"bookLoans": "Book loans"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"actions": {
|
||||||
|
"newItem": "New Item",
|
||||||
|
"addInviteUser": "Add/Invite User",
|
||||||
|
"filter": "Filter",
|
||||||
|
"downloadCsv": "Download CSV",
|
||||||
|
"uploadCsv": "Upload CSV",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete",
|
||||||
|
"apply": "Apply",
|
||||||
|
"deleting": "Deleting...",
|
||||||
|
"view": "View",
|
||||||
|
"edit": "Edit"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"allowedFormats": "Allowed formats: {{formats}}",
|
||||||
|
"clickToUpload": "Click to upload",
|
||||||
|
"dragAndDrop": "or drag and drop"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"value": "Value",
|
||||||
|
"selectValue": "Select Value",
|
||||||
|
"from": "From",
|
||||||
|
"to": "To",
|
||||||
|
"contains": "Contains",
|
||||||
|
"contained": "Contained",
|
||||||
|
"action": "Action"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Please confirm",
|
||||||
|
"deleteItem": "Are you sure you want to delete this item?"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"row": "Row",
|
||||||
|
"rows": "Rows"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
186
frontend/public/locales/pt/common.json
Normal file
186
frontend/public/locales/pt/common.json
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"dashboard": {
|
||||||
|
"pageTitle": "Painel",
|
||||||
|
"overview": "Visão geral",
|
||||||
|
"loadingWidgets": "A carregar widgets...",
|
||||||
|
"loading": "A carregar...",
|
||||||
|
"roleWidgets": "Widgets de {{role}}"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"pageTitle": "Entrar",
|
||||||
|
"sampleCredentialsSuperAdmin": "Usar para entrar como Super Admin:",
|
||||||
|
"sampleCredentialsAdmin": "Usar para entrar como Administrador:",
|
||||||
|
"sampleCredentialsUser": "Usar para entrar como Utilizador:",
|
||||||
|
"form": {
|
||||||
|
"loginLabel": "Login",
|
||||||
|
"loginHelp": "Introduza o seu login",
|
||||||
|
"passwordLabel": "Palavra-passe",
|
||||||
|
"passwordHelp": "Introduza a sua palavra-passe",
|
||||||
|
"remember": "Lembrar-me",
|
||||||
|
"forgotPassword": "Esqueceu a palavra-passe?",
|
||||||
|
"loginButton": "Entrar",
|
||||||
|
"loading": "A carregar...",
|
||||||
|
"noAccountYet": "Ainda não tem uma conta?",
|
||||||
|
"newAccount": "Nova conta"
|
||||||
|
},
|
||||||
|
"pexels": {
|
||||||
|
"photoCredit": "Foto de {{photographer}} no Pexels",
|
||||||
|
"videoCredit": "Vídeo de {{name}} no Pexels",
|
||||||
|
"videoUnsupported": "O seu navegador não suporta a tag de vídeo."
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"copyright": "© {{year}} {{title}}. Todos os direitos reservados",
|
||||||
|
"privacy": "Política de Privacidade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailHelp": "Introduza o seu email",
|
||||||
|
"passwordLabel": "Palavra-passe",
|
||||||
|
"passwordHelp": "Introduza a sua palavra-passe",
|
||||||
|
"confirmPasswordLabel": "Confirmar palavra-passe",
|
||||||
|
"confirmPasswordHelp": "Confirme a sua palavra-passe",
|
||||||
|
"loading": "A carregar...",
|
||||||
|
"login": "Entrar",
|
||||||
|
"checkEmailVerification": "Verifique o seu email para aceder ao link de verificação",
|
||||||
|
"genericError": "Algo correu mal. Tente novamente"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"pageTitle": "Registar",
|
||||||
|
"organization": "Organização",
|
||||||
|
"selectOrganization": "Selecione a organização...",
|
||||||
|
"submit": "Registar"
|
||||||
|
},
|
||||||
|
"forgot": {
|
||||||
|
"pageTitle": "Recuperar palavra-passe",
|
||||||
|
"submit": "Submeter"
|
||||||
|
},
|
||||||
|
"verifyEmail": {
|
||||||
|
"pageTitle": "Verificar email",
|
||||||
|
"success": "O seu email foi verificado"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"setTitle": "Definir palavra-passe",
|
||||||
|
"resetTitle": "Repor palavra-passe",
|
||||||
|
"enterNewPassword": "Introduza a nova palavra-passe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"widgetCreator": {
|
||||||
|
"title": "Criar gráfico ou widget",
|
||||||
|
"helpText": "Descreva o novo widget ou gráfico em linguagem natural. Por exemplo: \"Número de utilizadores administradores\" ou \"gráfico vermelho com contratos fechados agrupados por mês\"",
|
||||||
|
"settingsTitle": "Definições do criador de widgets",
|
||||||
|
"settingsDescription": "Para que função estamos a mostrar e criar widgets?",
|
||||||
|
"doneButton": "Concluído",
|
||||||
|
"loading": "A carregar...",
|
||||||
|
"creationError": "Erro ao criar widget"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Pesquisar",
|
||||||
|
"required": "Obrigatório",
|
||||||
|
"minLength": "Comprimento mínimo: {{count}} caracteres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"aside": {
|
||||||
|
"dashboard": "Painel",
|
||||||
|
"users": "Utilizadores",
|
||||||
|
"roles": "Funções",
|
||||||
|
"permissions": "Permissões",
|
||||||
|
"schoolOnboarding": "Integração de escola",
|
||||||
|
"academicMvp": "MVP Académico",
|
||||||
|
"schoolsCrud": "Escolas CRUD",
|
||||||
|
"students": "Alunos",
|
||||||
|
"guardians": "Encarregados",
|
||||||
|
"studentGuardians": "Alunos e encarregados",
|
||||||
|
"teachers": "Professores",
|
||||||
|
"courses": "Cursos",
|
||||||
|
"grades": "Notas",
|
||||||
|
"classes": "Turmas",
|
||||||
|
"subjects": "Disciplinas",
|
||||||
|
"enrollments": "Matrículas",
|
||||||
|
"assessments": "Avaliações",
|
||||||
|
"attendance": "Presenças",
|
||||||
|
"invoices": "Faturas",
|
||||||
|
"payments": "Pagamentos",
|
||||||
|
"employees": "Funcionários",
|
||||||
|
"products": "Produtos",
|
||||||
|
"books": "Livros",
|
||||||
|
"bookLoans": "Empréstimos de livros",
|
||||||
|
"profile": "Perfil",
|
||||||
|
"swaggerApi": "Swagger API"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"myProfile": "O meu perfil",
|
||||||
|
"logOut": "Sair",
|
||||||
|
"lightDark": "Claro/Escuro"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"footer": {
|
||||||
|
"madeWith": "Criado manualmente e feito com ❤️"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entities": {
|
||||||
|
"users": "Utilizadores",
|
||||||
|
"roles": "Funções",
|
||||||
|
"permissions": "Permissões",
|
||||||
|
"schools": "Escolas",
|
||||||
|
"students": "Alunos",
|
||||||
|
"guardians": "Encarregados",
|
||||||
|
"studentGuardians": "Alunos e encarregados",
|
||||||
|
"teachers": "Professores",
|
||||||
|
"courses": "Cursos",
|
||||||
|
"grades": "Notas",
|
||||||
|
"classes": "Turmas",
|
||||||
|
"subjects": "Disciplinas",
|
||||||
|
"enrollments": "Matrículas",
|
||||||
|
"assessments": "Avaliações",
|
||||||
|
"attendance": "Presenças",
|
||||||
|
"invoices": "Faturas",
|
||||||
|
"payments": "Pagamentos",
|
||||||
|
"employees": "Funcionários",
|
||||||
|
"products": "Produtos",
|
||||||
|
"books": "Livros",
|
||||||
|
"bookLoans": "Empréstimos de livros"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"actions": {
|
||||||
|
"newItem": "Novo item",
|
||||||
|
"addInviteUser": "Adicionar/convidar utilizador",
|
||||||
|
"filter": "Filtrar",
|
||||||
|
"downloadCsv": "Descarregar CSV",
|
||||||
|
"uploadCsv": "Carregar CSV",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"apply": "Aplicar",
|
||||||
|
"deleting": "A eliminar...",
|
||||||
|
"view": "Ver",
|
||||||
|
"edit": "Editar"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"allowedFormats": "Formatos permitidos: {{formats}}",
|
||||||
|
"clickToUpload": "Clique para carregar",
|
||||||
|
"dragAndDrop": "ou arraste e largue"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"value": "Valor",
|
||||||
|
"selectValue": "Selecionar valor",
|
||||||
|
"from": "De",
|
||||||
|
"to": "Até",
|
||||||
|
"contains": "Contém",
|
||||||
|
"contained": "Contido",
|
||||||
|
"action": "Ação"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Confirme, por favor",
|
||||||
|
"deleteItem": "Tem a certeza de que pretende eliminar este item?"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"row": "linha",
|
||||||
|
"rows": "linhas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import AsideMenuList from './AsideMenuList'
|
|||||||
import { MenuAsideItem } from '../interfaces'
|
import { MenuAsideItem } from '../interfaces'
|
||||||
import { useAppSelector } from '../stores/hooks'
|
import { useAppSelector } from '../stores/hooks'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuAsideItem
|
item: MenuAsideItem
|
||||||
@ -16,6 +17,8 @@ type Props = {
|
|||||||
const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
||||||
const [isLinkActive, setIsLinkActive] = useState(false)
|
const [isLinkActive, setIsLinkActive] = useState(false)
|
||||||
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
||||||
|
const [isMounted, setIsMounted] = useState(false)
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const asideMenuItemStyle = useAppSelector((state) => state.style.asideMenuItemStyle)
|
const asideMenuItemStyle = useAppSelector((state) => state.style.asideMenuItemStyle)
|
||||||
const asideMenuDropdownStyle = useAppSelector((state) => state.style.asideMenuDropdownStyle)
|
const asideMenuDropdownStyle = useAppSelector((state) => state.style.asideMenuDropdownStyle)
|
||||||
@ -28,6 +31,10 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
|||||||
|
|
||||||
const { asPath, isReady } = useRouter()
|
const { asPath, isReady } = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (item.href && isReady) {
|
if (item.href && isReady) {
|
||||||
const linkPathName = new URL(item.href, location.href).pathname + '/';
|
const linkPathName = new URL(item.href, location.href).pathname + '/';
|
||||||
@ -40,6 +47,10 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
|||||||
}
|
}
|
||||||
}, [item.href, isReady, asPath])
|
}, [item.href, isReady, asPath])
|
||||||
|
|
||||||
|
const translatedLabel = isMounted && item.labelKey
|
||||||
|
? t(item.labelKey, { defaultValue: item.label })
|
||||||
|
: item.label
|
||||||
|
|
||||||
const asideMenuItemInnerContents = (
|
const asideMenuItemInnerContents = (
|
||||||
<>
|
<>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
@ -50,7 +61,7 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
|||||||
item.menu ? '' : 'pr-12'
|
item.menu ? '' : 'pr-12'
|
||||||
} ${activeClassAddon}`}
|
} ${activeClassAddon}`}
|
||||||
>
|
>
|
||||||
{item.label}
|
{translatedLabel}
|
||||||
</span>
|
</span>
|
||||||
{item.menu && (
|
{item.menu && (
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
|
|||||||
@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js'
|
|||||||
import BaseIcon from './BaseIcon'
|
import BaseIcon from './BaseIcon'
|
||||||
import AsideMenuList from './AsideMenuList'
|
import AsideMenuList from './AsideMenuList'
|
||||||
import { MenuAsideItem } from '../interfaces'
|
import { MenuAsideItem } from '../interfaces'
|
||||||
import { useAppSelector } from '../stores/hooks'
|
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { useAppDispatch } from '../stores/hooks';
|
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -25,6 +26,16 @@ import { SlotInfo } from 'react-big-calendar';
|
|||||||
const perPage = 100
|
const perPage = 100
|
||||||
|
|
||||||
const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -286,7 +297,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -309,9 +320,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -320,7 +329,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -335,22 +344,22 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -364,13 +373,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -378,11 +385,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -392,11 +399,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -404,12 +411,12 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -422,13 +429,13 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -438,14 +445,14 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -476,7 +483,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import BaseIcon from './BaseIcon';
|
import BaseIcon from './BaseIcon';
|
||||||
import { mdiFileUploadOutline } from '@mdi/js';
|
import { mdiFileUploadOutline } from '@mdi/js';
|
||||||
|
|
||||||
@ -9,10 +10,19 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string, options = {}): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback, ...options })) : fallback
|
||||||
|
);
|
||||||
const [highlight, setHighlight] = useState(false);
|
const [highlight, setHighlight] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const fileInput = React.createRef<HTMLInputElement>();
|
const fileInput = React.createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!file && fileInput) fileInput.current.value = '';
|
if (!file && fileInput) fileInput.current.value = '';
|
||||||
}, [file, fileInput]);
|
}, [file, fileInput]);
|
||||||
@ -26,7 +36,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
|||||||
setFile(newFile);
|
setFile(newFile);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
} else {
|
} else {
|
||||||
setErrorMessage(`Allowed formats: ${formats}`);
|
setErrorMessage(translate('common.upload.allowedFormats', `Allowed formats: ${formats}`, { formats }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,8 +107,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className='mb-2 text-sm text-gray-500 dark:text-gray-400'>
|
<p className='mb-2 text-sm text-gray-500 dark:text-gray-400'>
|
||||||
<span className='font-semibold'>Click to upload</span> or drag
|
<span className='font-semibold'>{translate('common.upload.clickToUpload', 'Click to upload')}</span> {translate('common.upload.dragAndDrop', 'or drag and drop')}
|
||||||
and drop
|
|
||||||
</p>
|
</p>
|
||||||
{formats && (
|
{formats && (
|
||||||
<p className='text-xs text-gray-500 dark:text-gray-400'>
|
<p className='text-xs text-gray-500 dark:text-gray-400'>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -24,6 +25,16 @@ import ListGrades from './ListGrades';
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -279,7 +290,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -302,9 +313,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -313,7 +322,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -328,22 +337,22 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -357,13 +366,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -371,11 +378,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -385,11 +392,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -397,12 +404,12 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -415,13 +422,13 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -431,14 +438,14 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -463,7 +470,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -25,6 +26,16 @@ import axios from 'axios';
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -312,7 +323,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -335,9 +346,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -346,7 +355,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -361,22 +370,22 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -390,13 +399,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -404,11 +411,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -418,11 +425,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -430,12 +437,12 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -448,13 +455,13 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -464,14 +471,14 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -498,7 +505,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Select, { components, SingleValueProps, OptionProps } from 'react-select';
|
import Select, { components, SingleValueProps, OptionProps } from 'react-select';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type LanguageOption = { label: string; value: string };
|
type LanguageOption = { label: string; value: string };
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
menuPlacement?: 'auto' | 'bottom' | 'top';
|
||||||
|
width?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const LANGS: LanguageOption[] = [
|
const LANGS: LanguageOption[] = [
|
||||||
|
{ value: 'pt', label: '🇵🇹 PT' },
|
||||||
{ value: 'en', label: '🇬🇧 EN' },
|
{ value: 'en', label: '🇬🇧 EN' },
|
||||||
{ value: 'fr', label: '🇫🇷 FR' },
|
|
||||||
{ value: 'es', label: '🇪🇸 ES' },
|
|
||||||
{ value: 'de', label: '🇩🇪 DE' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const Option = (props: OptionProps<LanguageOption, false>) => (
|
const Option = (props: OptionProps<LanguageOption, false>) => (
|
||||||
@ -22,29 +27,33 @@ const SingleVal = (props: SingleValueProps<LanguageOption, false>) => (
|
|||||||
</components.SingleValue>
|
</components.SingleValue>
|
||||||
);
|
);
|
||||||
|
|
||||||
const LanguageSwitcher: React.FC = () => {
|
const LanguageSwitcher: React.FC<Props> = ({ className = '', menuPlacement = 'bottom', width = 88 }) => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [selected, setSelected] = useState<LanguageOption>(LANGS[0]);
|
const [selected, setSelected] = useState<LanguageOption>(LANGS[0]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const currentLanguage = i18n.resolvedLanguage || i18n.language || 'pt';
|
||||||
|
setSelected(LANGS.find((lang) => lang.value === currentLanguage) || LANGS[0]);
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, [i18n.language, i18n.resolvedLanguage]);
|
||||||
|
|
||||||
const handleChange = (opt: LanguageOption | null) => {
|
const handleChange = (opt: LanguageOption | null) => {
|
||||||
if (!opt) return;
|
if (!opt) return;
|
||||||
setSelected(opt);
|
setSelected(opt);
|
||||||
|
i18n.changeLanguage(opt.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!mounted) return null;
|
if (!mounted) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: 88 }}>
|
<div className={className} style={{ width }}>
|
||||||
<Select
|
<Select
|
||||||
value={selected}
|
value={selected}
|
||||||
options={LANGS}
|
options={LANGS}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
menuPlacement='top'
|
menuPlacement={menuPlacement}
|
||||||
components={{
|
components={{
|
||||||
Option,
|
Option,
|
||||||
SingleValue: SingleVal,
|
SingleValue: SingleVal,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import BaseIcon from './BaseIcon';
|
import BaseIcon from './BaseIcon';
|
||||||
import {
|
import {
|
||||||
@ -31,6 +31,16 @@ const ListActionsPopover = ({
|
|||||||
pathEdit,
|
pathEdit,
|
||||||
pathView,
|
pathView,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
@ -81,7 +91,7 @@ const ListActionsPopover = ({
|
|||||||
href={linkView}
|
href={linkView}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
View
|
{translate('common.actions.view', 'View')}
|
||||||
</Button>
|
</Button>
|
||||||
{hasUpdatePermission && (
|
{hasUpdatePermission && (
|
||||||
<Button
|
<Button
|
||||||
@ -90,7 +100,7 @@ const ListActionsPopover = ({
|
|||||||
href={linkEdit}
|
href={linkEdit}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
Edit
|
{translate('common.actions.edit', 'Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{hasUpdatePermission && (
|
{hasUpdatePermission && (
|
||||||
@ -103,7 +113,7 @@ const ListActionsPopover = ({
|
|||||||
}}
|
}}
|
||||||
sx={{ justifyContent: "start" }}
|
sx={{ justifyContent: "start" }}
|
||||||
>
|
>
|
||||||
Delete
|
{translate('common.actions.delete', 'Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, {useEffect, useRef} from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
|
||||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||||
import BaseDivider from './BaseDivider'
|
import BaseDivider from './BaseDivider'
|
||||||
import BaseIcon from './BaseIcon'
|
import BaseIcon from './BaseIcon'
|
||||||
@ -12,6 +11,7 @@ import { setDarkMode } from '../stores/styleSlice'
|
|||||||
import { logoutUser } from '../stores/authSlice'
|
import { logoutUser } from '../stores/authSlice'
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import ClickOutside from "./ClickOutside";
|
import ClickOutside from "./ClickOutside";
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: MenuNavBarItem
|
item: MenuNavBarItem
|
||||||
@ -33,6 +33,12 @@ export default function NavBarItem({ item }: Props) {
|
|||||||
const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`;
|
const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`;
|
||||||
|
|
||||||
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
||||||
|
const [isMounted, setIsMounted] = useState(false)
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => setIsDropdownActive(false);
|
return () => setIsDropdownActive(false);
|
||||||
@ -47,7 +53,11 @@ export default function NavBarItem({ item }: Props) {
|
|||||||
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
|
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
|
||||||
].join(' ')
|
].join(' ')
|
||||||
|
|
||||||
const itemLabel = item.isCurrentUser ? userName : item.label
|
const fallbackLabel = item.label ?? ''
|
||||||
|
const translatedLabel = isMounted && item.labelKey
|
||||||
|
? t(item.labelKey, { defaultValue: fallbackLabel })
|
||||||
|
: fallbackLabel
|
||||||
|
const itemLabel = item.isCurrentUser ? userName : translatedLabel
|
||||||
|
|
||||||
const handleMenuClick = () => {
|
const handleMenuClick = () => {
|
||||||
if (item.menu) {
|
if (item.menu) {
|
||||||
@ -64,21 +74,16 @@ export default function NavBarItem({ item }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getItemId = (label) => {
|
const getItemId = () => {
|
||||||
switch (label) {
|
if (item.isToggleLightDark) return 'themeToggle';
|
||||||
case 'Light/Dark':
|
if (item.isLogout) return 'logout';
|
||||||
return 'themeToggle';
|
return undefined;
|
||||||
case 'Log out':
|
|
||||||
return 'logout';
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavBarItemComponentContents = (
|
const NavBarItemComponentContents = (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
id={getItemId(itemLabel)}
|
id={getItemId()}
|
||||||
className={`flex items-center ${
|
className={`flex items-center ${
|
||||||
item.menu
|
item.menu
|
||||||
? 'bg-gray-100 dark:bg-dark-800 lg:bg-transparent lg:dark:bg-transparent p-3 lg:p-0'
|
? 'bg-gray-100 dark:bg-dark-800 lg:bg-transparent lg:dark:bg-transparent p-3 lg:p-0'
|
||||||
|
|||||||
@ -13,8 +13,14 @@ import BaseButtons from '../components/BaseButtons';
|
|||||||
import BaseButton from '../components/BaseButton';
|
import BaseButton from '../components/BaseButton';
|
||||||
import { passwordReset } from '../stores/authSlice';
|
import { passwordReset } from '../stores/authSlice';
|
||||||
import {useAppDispatch} from '../stores/hooks';
|
import {useAppDispatch} from '../stores/hooks';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function PasswordSetOrReset() {
|
export default function PasswordSetOrReset() {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [isInvitation, setIsInvitation] = React.useState(false);
|
const [isInvitation, setIsInvitation] = React.useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -24,6 +30,10 @@ export default function PasswordSetOrReset() {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (invitation) {
|
if (invitation) {
|
||||||
setIsInvitation(true);
|
setIsInvitation(true);
|
||||||
@ -49,16 +59,16 @@ export default function PasswordSetOrReset() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
{isInvitation && <title>{getPageTitle('Set Password')}</title>}
|
{isInvitation && <title>{getPageTitle(translate('pages.password.setTitle', 'Set Password'))}</title>}
|
||||||
{!isInvitation && <title>{getPageTitle('Reset Password')}</title>}
|
{!isInvitation && <title>{getPageTitle(translate('pages.password.resetTitle', 'Reset Password'))}</title>}
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
<div className='w-full flex flex-col items-center justify-center'>
|
<div className='w-full flex flex-col items-center justify-center'>
|
||||||
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
||||||
{isInvitation && <p className='text-xl mb-2'>Set Password</p>}
|
{isInvitation && <p className='text-xl mb-2'>{translate('pages.password.setTitle', 'Set Password')}</p>}
|
||||||
{!isInvitation && <p className='text-xl mb-2'>Reset Password</p>}
|
{!isInvitation && <p className='text-xl mb-2'>{translate('pages.password.resetTitle', 'Reset Password')}</p>}
|
||||||
<p className='text-base mb-4'>Enter your new password</p>
|
<p className='text-base mb-4'>{translate('pages.password.enterNewPassword', 'Enter your new password')}</p>
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
@ -74,7 +84,7 @@ export default function PasswordSetOrReset() {
|
|||||||
<Field
|
<Field
|
||||||
type='password'
|
type='password'
|
||||||
name='password'
|
name='password'
|
||||||
placeholder='Password'
|
placeholder={translate('pages.auth.passwordLabel', 'Password')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
@ -82,7 +92,7 @@ export default function PasswordSetOrReset() {
|
|||||||
<Field
|
<Field
|
||||||
type='password'
|
type='password'
|
||||||
name='confirm'
|
name='confirm'
|
||||||
placeholder='Confirm Password'
|
placeholder={translate('pages.auth.confirmPasswordLabel', 'Confirm Password')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -93,10 +103,10 @@ export default function PasswordSetOrReset() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
label={
|
label={
|
||||||
loading
|
loading
|
||||||
? 'Loading...'
|
? translate('pages.auth.loading', 'Loading...')
|
||||||
: isInvitation
|
: isInvitation
|
||||||
? 'Set Password'
|
? translate('pages.password.setTitle', 'Set Password')
|
||||||
: 'Reset Password'
|
: translate('pages.password.resetTitle', 'Reset Password')
|
||||||
}
|
}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -43,9 +43,9 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
headerName: 'Name',
|
headerName: 'Escola',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 180,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
@ -55,6 +55,46 @@ export const loadColumns = async (
|
|||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'nif',
|
||||||
|
headerName: 'NIF',
|
||||||
|
flex: 0.7,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'province',
|
||||||
|
headerName: 'Província',
|
||||||
|
flex: 0.8,
|
||||||
|
minWidth: 140,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'municipality',
|
||||||
|
headerName: 'Município',
|
||||||
|
flex: 0.8,
|
||||||
|
minWidth: 140,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
headerName: 'Estado',
|
||||||
|
flex: 0.7,
|
||||||
|
minWidth: 130,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: 'actions',
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Field, Form, Formik } from 'formik';
|
import { Field, Form, Formik } from 'formik';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useAppSelector } from '../stores/hooks';
|
import { useAppSelector } from '../stores/hooks';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const validateSearch = (value) => {
|
const validateSearch = (value) => {
|
||||||
let error;
|
let error;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
error = 'Required';
|
error = isMounted ? t('components.search.required') : 'Required';
|
||||||
} else if (value.length < 2) {
|
} else if (value.length < 2) {
|
||||||
error = 'Minimum length: 2 characters';
|
error = isMounted ? t('components.search.minLength', { count: 2 }) : 'Minimum length: 2 characters';
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
@ -36,7 +43,7 @@ const Search = () => {
|
|||||||
id='search'
|
id='search'
|
||||||
name='search'
|
name='search'
|
||||||
validate={validateSearch}
|
validate={validateSearch}
|
||||||
placeholder='Search'
|
placeholder={isMounted ? t('components.search.placeholder') : 'Search'}
|
||||||
className={` ${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-2 relative ml-2 w-full dark:placeholder-dark-600 ${focusRing} shadow-none`}
|
className={` ${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-2 relative ml-2 w-full dark:placeholder-dark-600 ${focusRing} shadow-none`}
|
||||||
/>
|
/>
|
||||||
{errors.search && touched.search && values.search.length < 2 ? (
|
{errors.search && touched.search && values.search.length < 2 ? (
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -24,6 +25,16 @@ import ListSubjects from './ListSubjects';
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -279,7 +290,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -302,9 +313,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -313,7 +322,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -328,22 +337,22 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -357,13 +366,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -371,11 +378,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -385,11 +392,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -397,12 +404,12 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -415,13 +422,13 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -431,14 +438,14 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -463,7 +470,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import React, { useEffect, useState, useMemo } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
|||||||
const perPage = 10
|
const perPage = 10
|
||||||
|
|
||||||
const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -277,7 +288,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
return (
|
return (
|
||||||
<div key={filterItem.id} className="flex mb-4">
|
<div key={filterItem.id} className="flex mb-4">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Filter</div>
|
<div className=" text-gray-500 font-bold">{translate('common.actions.filter', 'Filter')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='selectedField'
|
name='selectedField'
|
||||||
@ -300,9 +311,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.type === 'enum' ? (
|
)?.type === 'enum' ? (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className="text-gray-500 font-bold">
|
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||||
Value
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name="filterValue"
|
name="filterValue"
|
||||||
@ -311,7 +320,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
>
|
>
|
||||||
<option value="">Select Value</option>
|
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||||
{filters.find((filter) =>
|
{filters.find((filter) =>
|
||||||
filter.title === filterItem?.fields?.selectedField
|
filter.title === filterItem?.fields?.selectedField
|
||||||
)?.options?.map((option) => (
|
)?.options?.map((option) => (
|
||||||
@ -326,22 +335,22 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.number ? (
|
)?.number ? (
|
||||||
<div className="flex flex-row w-full mr-3">
|
<div className="flex flex-row w-full mr-3">
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">From</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.from', 'From')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<div className=" text-gray-500 font-bold">To</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -355,13 +364,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
)?.date ? (
|
)?.date ? (
|
||||||
<div className='flex flex-row w-full mr-3'>
|
<div className='flex flex-row w-full mr-3'>
|
||||||
<div className='flex flex-col w-full mr-3'>
|
<div className='flex flex-col w-full mr-3'>
|
||||||
<div className=' text-gray-500 font-bold'>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.from', 'From')}</div>
|
||||||
From
|
|
||||||
</div>
|
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueFrom'
|
name='filterValueFrom'
|
||||||
placeholder='From'
|
placeholder={translate('common.filters.from', 'From')}
|
||||||
id='filterValueFrom'
|
id='filterValueFrom'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueFrom || ''}
|
value={filterItem?.fields?.filterValueFrom || ''}
|
||||||
@ -369,11 +376,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<div className=' text-gray-500 font-bold'>To</div>
|
<div className=' text-gray-500 font-bold'>{translate('common.filters.to', 'To')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValueTo'
|
name='filterValueTo'
|
||||||
placeholder='to'
|
placeholder={translate('common.filters.to', 'to')}
|
||||||
id='filterValueTo'
|
id='filterValueTo'
|
||||||
type='datetime-local'
|
type='datetime-local'
|
||||||
value={filterItem?.fields?.filterValueTo || ''}
|
value={filterItem?.fields?.filterValueTo || ''}
|
||||||
@ -383,11 +390,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col w-full mr-3">
|
<div className="flex flex-col w-full mr-3">
|
||||||
<div className=" text-gray-500 font-bold">Contains</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.contains', 'Contains')}</div>
|
||||||
<Field
|
<Field
|
||||||
className={controlClasses}
|
className={controlClasses}
|
||||||
name='filterValue'
|
name='filterValue'
|
||||||
placeholder='Contained'
|
placeholder={translate('common.filters.contained', 'Contained')}
|
||||||
id='filterValue'
|
id='filterValue'
|
||||||
value={filterItem?.fields?.filterValue || ''}
|
value={filterItem?.fields?.filterValue || ''}
|
||||||
onChange={handleChange(filterItem.id)}
|
onChange={handleChange(filterItem.id)}
|
||||||
@ -395,12 +402,12 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className=" text-gray-500 font-bold">Action</div>
|
<div className=" text-gray-500 font-bold">{translate('common.filters.action', 'Action')}</div>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
type='reset'
|
type='reset'
|
||||||
color='danger'
|
color='danger'
|
||||||
label='Delete'
|
label={translate('common.actions.delete', 'Delete')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteFilter(filterItem.id)
|
deleteFilter(filterItem.id)
|
||||||
}}
|
}}
|
||||||
@ -413,13 +420,13 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2 mr-3"
|
className="my-2 mr-3"
|
||||||
color="success"
|
color="success"
|
||||||
label='Apply'
|
label={translate('common.actions.apply', 'Apply')}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className="my-2"
|
className="my-2"
|
||||||
color='info'
|
color='info'
|
||||||
label='Cancel'
|
label={translate('common.actions.cancel', 'Cancel')}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -429,14 +436,14 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
</CardBox> : null
|
</CardBox> : null
|
||||||
}
|
}
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title="Please confirm"
|
title={translate('common.confirm.title', 'Please confirm')}
|
||||||
buttonColor="info"
|
buttonColor="info"
|
||||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||||
isActive={isModalTrashActive}
|
isActive={isModalTrashActive}
|
||||||
onConfirm={handleDeleteAction}
|
onConfirm={handleDeleteAction}
|
||||||
onCancel={handleModalAction}
|
onCancel={handleModalAction}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to delete this item?</p>
|
<p>{translate('common.confirm.deleteItem', 'Are you sure you want to delete this item?')}</p>
|
||||||
</CardBoxModal>
|
</CardBoxModal>
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +457,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className='me-4'
|
className='me-4'
|
||||||
color='danger'
|
color='danger'
|
||||||
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
label={`${translate('common.actions.delete', 'Delete')} ${selectedRows.length === 1 ? translate('common.table.row', 'Row') : translate('common.table.rows', 'Rows')}`}
|
||||||
onClick={() => onDeleteRows(selectedRows)}
|
onClick={() => onDeleteRows(selectedRows)}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('delete-rows-button'),
|
document.getElementById('delete-rows-button'),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Field, Form, Formik } from 'formik';
|
|||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import FormField from '../FormField';
|
import FormField from '../FormField';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
aiPrompt,
|
aiPrompt,
|
||||||
setErrorNotification,
|
setErrorNotification,
|
||||||
@ -24,10 +25,19 @@ export const WidgetCreator = ({
|
|||||||
widgetsRole,
|
widgetsRole,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
||||||
const { notify: openAiNotify } = useAppSelector((state) => state.openAi);
|
const { notify: openAiNotify } = useAppSelector((state) => state.openAi);
|
||||||
|
|
||||||
const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
|
const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (openAiNotify.showNotification) {
|
if (openAiNotify.showNotification) {
|
||||||
notify(openAiNotify.typeNotification, openAiNotify.textNotification);
|
notify(openAiNotify.typeNotification, openAiNotify.textNotification);
|
||||||
@ -67,7 +77,7 @@ export const WidgetCreator = ({
|
|||||||
const errorMessage =
|
const errorMessage =
|
||||||
responcePayload.data?.error?.message || error?.message;
|
responcePayload.data?.error?.message || error?.message;
|
||||||
await dispatch(
|
await dispatch(
|
||||||
setErrorNotification(errorMessage || 'Error with widget creation'),
|
setErrorNotification(errorMessage || translate('components.widgetCreator.creationError', 'Error with widget creation')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -90,11 +100,11 @@ export const WidgetCreator = ({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormField
|
<FormField
|
||||||
label='Create Chart or Widget'
|
label={translate('components.widgetCreator.title', 'Create Chart or Widget')}
|
||||||
help={
|
help={
|
||||||
isFetchingQuery ?
|
isFetchingQuery ?
|
||||||
'Loading...' :
|
translate('components.widgetCreator.loading', 'Loading...') :
|
||||||
'Describe your new widget or chart in natural language. For example: "Number of admin users" OR "red chart with number of closed contracts grouped by month"'
|
translate('components.widgetCreator.helpText', 'Describe your new widget or chart in natural language. For example: "Number of admin users" OR "red chart with number of closed contracts grouped by month"')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Field type='input' name='description' disabled={isFetchingQuery} />
|
<Field type='input' name='description' disabled={isFetchingQuery} />
|
||||||
@ -110,14 +120,14 @@ export const WidgetCreator = ({
|
|||||||
>
|
>
|
||||||
{({ submitForm }) => (
|
{({ submitForm }) => (
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Widget Creator Settings'
|
title={translate('components.widgetCreator.settingsTitle', 'Widget Creator Settings')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel='Done'
|
buttonLabel={translate('components.widgetCreator.doneButton', 'Done')}
|
||||||
isActive={isModalOpen}
|
isActive={isModalOpen}
|
||||||
onConfirm={submitForm}
|
onConfirm={submitForm}
|
||||||
onCancel={() => setIsModalOpen(false)}
|
onCancel={() => setIsModalOpen(false)}
|
||||||
>
|
>
|
||||||
<p>What role are we showing and creating widgets for?</p>
|
<p>{translate('components.widgetCreator.settingsDescription', 'What role are we showing and creating widgets for?')}</p>
|
||||||
|
|
||||||
<Form>
|
<Form>
|
||||||
<FormField>
|
<FormField>
|
||||||
|
|||||||
@ -8,9 +8,13 @@ i18n
|
|||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'pt',
|
||||||
|
supportedLngs: ['pt', 'en'],
|
||||||
|
load: 'languageOnly',
|
||||||
|
defaultNS: 'common',
|
||||||
|
ns: ['common'],
|
||||||
detection: {
|
detection: {
|
||||||
order: ['localStorage', 'navigator'],
|
order: ['localStorage'],
|
||||||
lookupLocalStorage: 'app_lang_',
|
lookupLocalStorage: 'app_lang_',
|
||||||
caches: ['localStorage'],
|
caches: ['localStorage'],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export type UserPayloadObject = {
|
|||||||
|
|
||||||
export type MenuAsideItem = {
|
export type MenuAsideItem = {
|
||||||
label: string
|
label: string
|
||||||
|
labelKey?: string
|
||||||
icon?: string
|
icon?: string
|
||||||
href?: string
|
href?: string
|
||||||
target?: string
|
target?: string
|
||||||
@ -18,6 +19,7 @@ export type MenuAsideItem = {
|
|||||||
|
|
||||||
export type MenuNavBarItem = {
|
export type MenuNavBarItem = {
|
||||||
label?: string
|
label?: string
|
||||||
|
labelKey?: string
|
||||||
icon?: string
|
icon?: string
|
||||||
href?: string
|
href?: string
|
||||||
target?: string
|
target?: string
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { ReactNode, useEffect } from 'react'
|
import React, { ReactNode, useEffect, useState } from 'react'
|
||||||
import { useState } from 'react'
|
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||||
import menuAside from '../menuAside'
|
import menuAside from '../menuAside'
|
||||||
@ -13,6 +12,8 @@ import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
|||||||
import Search from '../components/Search';
|
import Search from '../components/Search';
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import {findMe, logoutUser} from "../stores/authSlice";
|
import {findMe, logoutUser} from "../stores/authSlice";
|
||||||
|
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../helpers/userPermissions";
|
import {hasPermission} from "../helpers/userPermissions";
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ export default function LayoutAuthenticated({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { t } = useTranslation('common')
|
||||||
const { token, currentUser } = useAppSelector((state) => state.auth)
|
const { token, currentUser } = useAppSelector((state) => state.auth)
|
||||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||||
let localToken
|
let localToken
|
||||||
@ -69,6 +71,11 @@ export default function LayoutAuthenticated({
|
|||||||
|
|
||||||
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
|
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
|
||||||
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
|
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTranslationMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRouteChangeStart = () => {
|
const handleRouteChangeStart = () => {
|
||||||
@ -114,6 +121,9 @@ export default function LayoutAuthenticated({
|
|||||||
<NavBarItemPlain useMargin>
|
<NavBarItemPlain useMargin>
|
||||||
<Search />
|
<Search />
|
||||||
</NavBarItemPlain>
|
</NavBarItemPlain>
|
||||||
|
<NavBarItemPlain display="hidden md:flex" useMargin>
|
||||||
|
<LanguageSwitcher menuPlacement="bottom" />
|
||||||
|
</NavBarItemPlain>
|
||||||
</NavBar>
|
</NavBar>
|
||||||
<AsideMenu
|
<AsideMenu
|
||||||
isAsideMobileExpanded={isAsideMobileExpanded}
|
isAsideMobileExpanded={isAsideMobileExpanded}
|
||||||
@ -122,7 +132,7 @@ export default function LayoutAuthenticated({
|
|||||||
onAsideLgClose={() => setIsAsideLgActive(false)}
|
onAsideLgClose={() => setIsAsideLgActive(false)}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<FooterBar>Hand-crafted & Made with ❤️</FooterBar>
|
<FooterBar>{isTranslationMounted ? t('layout.footer.madeWith') : 'Hand-crafted & Made with ❤️'}</FooterBar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactNode } from 'react'
|
import React, { ReactNode } from 'react'
|
||||||
import { useAppSelector } from '../stores/hooks'
|
import { useAppSelector } from '../stores/hooks'
|
||||||
|
import LanguageSwitcher from '../components/LanguageSwitcher'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -11,7 +12,12 @@ export default function LayoutGuest({ children }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={darkMode ? 'dark' : ''}>
|
<div className={darkMode ? 'dark' : ''}>
|
||||||
<div className={`${bgColor} dark:bg-slate-800 dark:text-slate-100`}>{children}</div>
|
<div className={`${bgColor} dark:bg-slate-800 dark:text-slate-100`}>
|
||||||
|
<div className="fixed right-4 top-4 z-50">
|
||||||
|
<LanguageSwitcher menuPlacement="bottom" />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,13 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
href: '/dashboard',
|
href: '/dashboard',
|
||||||
icon: icon.mdiViewDashboardOutline,
|
icon: icon.mdiViewDashboardOutline,
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
|
labelKey: 'navigation.aside.dashboard',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
href: '/users/users-list',
|
href: '/users/users-list',
|
||||||
label: 'Users',
|
label: 'Users',
|
||||||
|
labelKey: 'navigation.aside.users',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
||||||
@ -19,6 +21,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/roles/roles-list',
|
href: '/roles/roles-list',
|
||||||
label: 'Roles',
|
label: 'Roles',
|
||||||
|
labelKey: 'navigation.aside.roles',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
||||||
@ -27,14 +30,32 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/permissions/permissions-list',
|
href: '/permissions/permissions-list',
|
||||||
label: 'Permissions',
|
label: 'Permissions',
|
||||||
|
labelKey: 'navigation.aside.permissions',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||||
permissions: 'READ_PERMISSIONS'
|
permissions: 'READ_PERMISSIONS'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: '/schools/onboarding',
|
||||||
|
label: 'School Onboarding',
|
||||||
|
labelKey: 'navigation.aside.schoolOnboarding',
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
icon: 'mdiSchoolOutline' in icon ? icon['mdiSchoolOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
|
permissions: 'READ_SCHOOLS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/academic/mvp',
|
||||||
|
label: 'MVP Académico',
|
||||||
|
labelKey: 'navigation.aside.academicMvp',
|
||||||
|
icon: icon.mdiBookEducationOutline,
|
||||||
|
permissions: 'READ_STUDENTS'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: '/schools/schools-list',
|
href: '/schools/schools-list',
|
||||||
label: 'Schools',
|
label: 'Schools CRUD',
|
||||||
|
labelKey: 'navigation.aside.schoolsCrud',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: icon.mdiTable ?? icon.mdiTable,
|
icon: icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -43,6 +64,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/students/students-list',
|
href: '/students/students-list',
|
||||||
label: 'Students',
|
label: 'Students',
|
||||||
|
labelKey: 'navigation.aside.students',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiAccountSchool' in icon ? icon['mdiAccountSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiAccountSchool' in icon ? icon['mdiAccountSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -51,6 +73,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/guardians/guardians-list',
|
href: '/guardians/guardians-list',
|
||||||
label: 'Guardians',
|
label: 'Guardians',
|
||||||
|
labelKey: 'navigation.aside.guardians',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiAccountGroup' in icon ? icon['mdiAccountGroup' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiAccountGroup' in icon ? icon['mdiAccountGroup' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -59,6 +82,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/student_guardians/student_guardians-list',
|
href: '/student_guardians/student_guardians-list',
|
||||||
label: 'Student guardians',
|
label: 'Student guardians',
|
||||||
|
labelKey: 'navigation.aside.studentGuardians',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -67,6 +91,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/teachers/teachers-list',
|
href: '/teachers/teachers-list',
|
||||||
label: 'Teachers',
|
label: 'Teachers',
|
||||||
|
labelKey: 'navigation.aside.teachers',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiAccountTie' in icon ? icon['mdiAccountTie' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiAccountTie' in icon ? icon['mdiAccountTie' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -75,6 +100,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/courses/courses-list',
|
href: '/courses/courses-list',
|
||||||
label: 'Courses',
|
label: 'Courses',
|
||||||
|
labelKey: 'navigation.aside.courses',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiBookOpenPageVariant' in icon ? icon['mdiBookOpenPageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiBookOpenPageVariant' in icon ? icon['mdiBookOpenPageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -83,6 +109,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/grades/grades-list',
|
href: '/grades/grades-list',
|
||||||
label: 'Grades',
|
label: 'Grades',
|
||||||
|
labelKey: 'navigation.aside.grades',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiNumeric' in icon ? icon['mdiNumeric' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiNumeric' in icon ? icon['mdiNumeric' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -91,6 +118,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/classes/classes-list',
|
href: '/classes/classes-list',
|
||||||
label: 'Classes',
|
label: 'Classes',
|
||||||
|
labelKey: 'navigation.aside.classes',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiGoogleClassroom' in icon ? icon['mdiGoogleClassroom' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiGoogleClassroom' in icon ? icon['mdiGoogleClassroom' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -99,6 +127,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/subjects/subjects-list',
|
href: '/subjects/subjects-list',
|
||||||
label: 'Subjects',
|
label: 'Subjects',
|
||||||
|
labelKey: 'navigation.aside.subjects',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiBookmarkMultiple' in icon ? icon['mdiBookmarkMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiBookmarkMultiple' in icon ? icon['mdiBookmarkMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -107,6 +136,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/enrollments/enrollments-list',
|
href: '/enrollments/enrollments-list',
|
||||||
label: 'Enrollments',
|
label: 'Enrollments',
|
||||||
|
labelKey: 'navigation.aside.enrollments',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiClipboardText' in icon ? icon['mdiClipboardText' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiClipboardText' in icon ? icon['mdiClipboardText' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -115,6 +145,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/assessments/assessments-list',
|
href: '/assessments/assessments-list',
|
||||||
label: 'Assessments',
|
label: 'Assessments',
|
||||||
|
labelKey: 'navigation.aside.assessments',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiNotebookEdit' in icon ? icon['mdiNotebookEdit' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiNotebookEdit' in icon ? icon['mdiNotebookEdit' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -123,6 +154,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/attendance/attendance-list',
|
href: '/attendance/attendance-list',
|
||||||
label: 'Attendance',
|
label: 'Attendance',
|
||||||
|
labelKey: 'navigation.aside.attendance',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiCalendarCheck' in icon ? icon['mdiCalendarCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiCalendarCheck' in icon ? icon['mdiCalendarCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -131,6 +163,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/invoices/invoices-list',
|
href: '/invoices/invoices-list',
|
||||||
label: 'Invoices',
|
label: 'Invoices',
|
||||||
|
labelKey: 'navigation.aside.invoices',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -139,6 +172,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/payments/payments-list',
|
href: '/payments/payments-list',
|
||||||
label: 'Payments',
|
label: 'Payments',
|
||||||
|
labelKey: 'navigation.aside.payments',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiCashCheck' in icon ? icon['mdiCashCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiCashCheck' in icon ? icon['mdiCashCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -147,6 +181,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/employees/employees-list',
|
href: '/employees/employees-list',
|
||||||
label: 'Employees',
|
label: 'Employees',
|
||||||
|
labelKey: 'navigation.aside.employees',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiBadgeAccount' in icon ? icon['mdiBadgeAccount' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiBadgeAccount' in icon ? icon['mdiBadgeAccount' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -155,6 +190,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/products/products-list',
|
href: '/products/products-list',
|
||||||
label: 'Products',
|
label: 'Products',
|
||||||
|
labelKey: 'navigation.aside.products',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiPackageVariant' in icon ? icon['mdiPackageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiPackageVariant' in icon ? icon['mdiPackageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -163,6 +199,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/books/books-list',
|
href: '/books/books-list',
|
||||||
label: 'Books',
|
label: 'Books',
|
||||||
|
labelKey: 'navigation.aside.books',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiLibrary' in icon ? icon['mdiLibrary' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiLibrary' in icon ? icon['mdiLibrary' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -171,6 +208,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/book_loans/book_loans-list',
|
href: '/book_loans/book_loans-list',
|
||||||
label: 'Book loans',
|
label: 'Book loans',
|
||||||
|
labelKey: 'navigation.aside.bookLoans',
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: 'mdiBookClock' in icon ? icon['mdiBookClock' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
icon: 'mdiBookClock' in icon ? icon['mdiBookClock' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||||
@ -179,6 +217,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
{
|
{
|
||||||
href: '/profile',
|
href: '/profile',
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
|
labelKey: 'navigation.aside.profile',
|
||||||
icon: icon.mdiAccountCircle,
|
icon: icon.mdiAccountCircle,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -187,6 +226,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
href: '/api-docs',
|
href: '/api-docs',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
label: 'Swagger API',
|
label: 'Swagger API',
|
||||||
|
labelKey: 'navigation.aside.swaggerApi',
|
||||||
icon: icon.mdiFileCode,
|
icon: icon.mdiFileCode,
|
||||||
permissions: 'READ_API_DOCS'
|
permissions: 'READ_API_DOCS'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,15 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
mdiMenu,
|
|
||||||
mdiClockOutline,
|
|
||||||
mdiCloud,
|
|
||||||
mdiCrop,
|
|
||||||
mdiAccount,
|
mdiAccount,
|
||||||
mdiCogOutline,
|
|
||||||
mdiEmail,
|
|
||||||
mdiLogout,
|
mdiLogout,
|
||||||
mdiThemeLightDark,
|
mdiThemeLightDark,
|
||||||
mdiGithub,
|
|
||||||
mdiVuejs,
|
|
||||||
} from '@mdi/js'
|
} from '@mdi/js'
|
||||||
import { MenuNavBarItem } from './interfaces'
|
import { MenuNavBarItem } from './interfaces'
|
||||||
|
|
||||||
@ -20,6 +12,7 @@ const menuNavBar: MenuNavBarItem[] = [
|
|||||||
{
|
{
|
||||||
icon: mdiAccount,
|
icon: mdiAccount,
|
||||||
label: 'My Profile',
|
label: 'My Profile',
|
||||||
|
labelKey: 'navigation.nav.myProfile',
|
||||||
href: '/profile',
|
href: '/profile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -28,6 +21,7 @@ const menuNavBar: MenuNavBarItem[] = [
|
|||||||
{
|
{
|
||||||
icon: mdiLogout,
|
icon: mdiLogout,
|
||||||
label: 'Log Out',
|
label: 'Log Out',
|
||||||
|
labelKey: 'navigation.nav.logOut',
|
||||||
isLogout: true,
|
isLogout: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -35,12 +29,14 @@ const menuNavBar: MenuNavBarItem[] = [
|
|||||||
{
|
{
|
||||||
icon: mdiThemeLightDark,
|
icon: mdiThemeLightDark,
|
||||||
label: 'Light/Dark',
|
label: 'Light/Dark',
|
||||||
|
labelKey: 'navigation.nav.lightDark',
|
||||||
isDesktopNoLabel: true,
|
isDesktopNoLabel: true,
|
||||||
isToggleLightDark: true,
|
isToggleLightDark: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: mdiLogout,
|
icon: mdiLogout,
|
||||||
label: 'Log out',
|
label: 'Log out',
|
||||||
|
labelKey: 'navigation.nav.logOut',
|
||||||
isDesktopNoLabel: true,
|
isDesktopNoLabel: true,
|
||||||
isLogout: true,
|
isLogout: true,
|
||||||
},
|
},
|
||||||
|
|||||||
729
frontend/src/pages/academic/mvp.tsx
Normal file
729
frontend/src/pages/academic/mvp.tsx
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
import {
|
||||||
|
mdiAccountSchool,
|
||||||
|
mdiBookEducationOutline,
|
||||||
|
mdiClipboardCheckOutline,
|
||||||
|
mdiGoogleClassroom,
|
||||||
|
mdiRefresh,
|
||||||
|
mdiSchoolOutline,
|
||||||
|
} from '@mdi/js'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import React, { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import BaseButton from '../../components/BaseButton'
|
||||||
|
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 { hasPermission } from '../../helpers/userPermissions'
|
||||||
|
import { useAppSelector } from '../../stores/hooks'
|
||||||
|
|
||||||
|
type OptionRecord = {
|
||||||
|
id: string
|
||||||
|
label?: string
|
||||||
|
name?: string
|
||||||
|
nome?: string
|
||||||
|
nome_completo?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type StudentRecord = OptionRecord & {
|
||||||
|
numero_processo?: string | null
|
||||||
|
sexo?: string | null
|
||||||
|
telefone?: string | null
|
||||||
|
email?: string | null
|
||||||
|
school?: OptionRecord | null
|
||||||
|
status?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClassRecord = OptionRecord & {
|
||||||
|
turno?: string | null
|
||||||
|
sala?: string | null
|
||||||
|
capacidade?: number | null
|
||||||
|
school?: OptionRecord | null
|
||||||
|
status?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnrollmentRecord = OptionRecord & {
|
||||||
|
ano_lectivo?: string | null
|
||||||
|
data_matricula?: string | null
|
||||||
|
status?: string | null
|
||||||
|
school?: OptionRecord | null
|
||||||
|
student?: StudentRecord | null
|
||||||
|
class?: ClassRecord | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchoolRecord = OptionRecord & {
|
||||||
|
status?: string | null
|
||||||
|
province?: string | null
|
||||||
|
municipality?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type StudentForm = {
|
||||||
|
school: string
|
||||||
|
numero_processo: string
|
||||||
|
nome_completo: string
|
||||||
|
sexo: 'masculino' | 'feminino' | 'outro'
|
||||||
|
data_nascimento: string
|
||||||
|
bi: string
|
||||||
|
telefone: string
|
||||||
|
email: string
|
||||||
|
morada: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClassForm = {
|
||||||
|
school: string
|
||||||
|
nome: string
|
||||||
|
turno: 'manha' | 'tarde' | 'noite'
|
||||||
|
sala: string
|
||||||
|
capacidade: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnrollmentForm = {
|
||||||
|
school: string
|
||||||
|
student: string
|
||||||
|
class: string
|
||||||
|
ano_lectivo: string
|
||||||
|
data_matricula: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentAcademicYear = '2026'
|
||||||
|
const today = new Date().toISOString().slice(0, 10)
|
||||||
|
|
||||||
|
const emptyStudentForm: StudentForm = {
|
||||||
|
school: '',
|
||||||
|
numero_processo: '',
|
||||||
|
nome_completo: '',
|
||||||
|
sexo: 'masculino',
|
||||||
|
data_nascimento: '',
|
||||||
|
bi: '',
|
||||||
|
telefone: '',
|
||||||
|
email: '',
|
||||||
|
morada: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyClassForm: ClassForm = {
|
||||||
|
school: '',
|
||||||
|
nome: '',
|
||||||
|
turno: 'manha',
|
||||||
|
sala: '',
|
||||||
|
capacidade: '35',
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyEnrollmentForm: EnrollmentForm = {
|
||||||
|
school: '',
|
||||||
|
student: '',
|
||||||
|
class: '',
|
||||||
|
ano_lectivo: currentAcademicYear,
|
||||||
|
data_matricula: today,
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputClass =
|
||||||
|
'mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 shadow-sm outline-none transition focus:border-emerald-500 focus:ring-4 focus:ring-emerald-100 dark:border-slate-700 dark:bg-slate-900 dark:text-white'
|
||||||
|
|
||||||
|
const labelClass = 'text-sm font-semibold text-slate-700 dark:text-slate-200'
|
||||||
|
|
||||||
|
function readableError(error: any) {
|
||||||
|
return error?.response?.data?.message || error?.message || 'Não foi possível concluir a operação.'
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordLabel(record?: OptionRecord | null) {
|
||||||
|
return record?.label || record?.name || record?.nome || record?.nome_completo || 'Sem nome'
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanText(value: string) {
|
||||||
|
const trimmed = value.trim()
|
||||||
|
return trimmed || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanStudentPayload(form: StudentForm) {
|
||||||
|
return {
|
||||||
|
school: cleanText(form.school),
|
||||||
|
numero_processo: cleanText(form.numero_processo),
|
||||||
|
nome_completo: cleanText(form.nome_completo),
|
||||||
|
sexo: form.sexo,
|
||||||
|
data_nascimento: cleanText(form.data_nascimento),
|
||||||
|
bi: cleanText(form.bi),
|
||||||
|
telefone: cleanText(form.telefone),
|
||||||
|
email: cleanText(form.email),
|
||||||
|
morada: cleanText(form.morada),
|
||||||
|
status: 'ativo',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanClassPayload(form: ClassForm) {
|
||||||
|
return {
|
||||||
|
school: cleanText(form.school),
|
||||||
|
nome: cleanText(form.nome),
|
||||||
|
turno: form.turno,
|
||||||
|
sala: cleanText(form.sala),
|
||||||
|
capacidade: Number(form.capacidade) || null,
|
||||||
|
status: 'ativa',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanEnrollmentPayload(form: EnrollmentForm) {
|
||||||
|
return {
|
||||||
|
school: cleanText(form.school),
|
||||||
|
student: cleanText(form.student),
|
||||||
|
class: cleanText(form.class),
|
||||||
|
ano_lectivo: cleanText(form.ano_lectivo),
|
||||||
|
data_matricula: cleanText(form.data_matricula),
|
||||||
|
status: 'ativa',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AcademicMvpPage = () => {
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth)
|
||||||
|
const canCreateStudents = currentUser && hasPermission(currentUser, 'CREATE_STUDENTS')
|
||||||
|
const canCreateClasses = currentUser && hasPermission(currentUser, 'CREATE_CLASSES')
|
||||||
|
const canCreateEnrollments = currentUser && hasPermission(currentUser, 'CREATE_ENROLLMENTS')
|
||||||
|
|
||||||
|
const [schools, setSchools] = useState<SchoolRecord[]>([])
|
||||||
|
const [students, setStudents] = useState<StudentRecord[]>([])
|
||||||
|
const [classes, setClasses] = useState<ClassRecord[]>([])
|
||||||
|
const [enrollments, setEnrollments] = useState<EnrollmentRecord[]>([])
|
||||||
|
const [counts, setCounts] = useState({ schools: 0, students: 0, classes: 0, enrollments: 0 })
|
||||||
|
const [studentForm, setStudentForm] = useState<StudentForm>(emptyStudentForm)
|
||||||
|
const [classForm, setClassForm] = useState<ClassForm>(emptyClassForm)
|
||||||
|
const [enrollmentForm, setEnrollmentForm] = useState<EnrollmentForm>(emptyEnrollmentForm)
|
||||||
|
const [activeWorkflow, setActiveWorkflow] = useState<'student' | 'class' | 'enrollment'>('student')
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isSavingStudent, setIsSavingStudent] = useState(false)
|
||||||
|
const [isSavingClass, setIsSavingClass] = useState(false)
|
||||||
|
const [isSavingEnrollment, setIsSavingEnrollment] = useState(false)
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
const selectedSchoolId = enrollmentForm.school || studentForm.school || classForm.school || schools[0]?.id || ''
|
||||||
|
|
||||||
|
const recentEnrollments = useMemo(() => enrollments.slice(0, 6), [enrollments])
|
||||||
|
const activeEnrollments = enrollments.filter((enrollment) => (enrollment.status || 'ativa') === 'ativa').length
|
||||||
|
const occupancy = classes.length ? Math.round((activeEnrollments / classes.length) * 10) / 10 : 0
|
||||||
|
|
||||||
|
const loadAcademicData = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [schoolsResponse, studentsResponse, classesResponse, enrollmentsResponse] = await Promise.all([
|
||||||
|
axios.get('/schools?limit=100&page=0'),
|
||||||
|
axios.get('/students?limit=100&page=0'),
|
||||||
|
axios.get('/classes?limit=100&page=0'),
|
||||||
|
axios.get('/enrollments?limit=100&page=0'),
|
||||||
|
])
|
||||||
|
|
||||||
|
const schoolRows = Array.isArray(schoolsResponse.data?.rows) ? schoolsResponse.data.rows : []
|
||||||
|
const studentRows = Array.isArray(studentsResponse.data?.rows) ? studentsResponse.data.rows : []
|
||||||
|
const classRows = Array.isArray(classesResponse.data?.rows) ? classesResponse.data.rows : []
|
||||||
|
const enrollmentRows = Array.isArray(enrollmentsResponse.data?.rows) ? enrollmentsResponse.data.rows : []
|
||||||
|
|
||||||
|
setSchools(schoolRows)
|
||||||
|
setStudents(studentRows)
|
||||||
|
setClasses(classRows)
|
||||||
|
setEnrollments(enrollmentRows)
|
||||||
|
setCounts({
|
||||||
|
schools: schoolsResponse.data?.count || schoolRows.length,
|
||||||
|
students: studentsResponse.data?.count || studentRows.length,
|
||||||
|
classes: classesResponse.data?.count || classRows.length,
|
||||||
|
enrollments: enrollmentsResponse.data?.count || enrollmentRows.length,
|
||||||
|
})
|
||||||
|
|
||||||
|
const firstSchool = selectedSchoolId || schoolRows[0]?.id || ''
|
||||||
|
const firstStudent = enrollmentForm.student || studentRows[0]?.id || ''
|
||||||
|
const firstClass = enrollmentForm.class || classRows[0]?.id || ''
|
||||||
|
|
||||||
|
setStudentForm((current) => ({ ...current, school: current.school || firstSchool }))
|
||||||
|
setClassForm((current) => ({ ...current, school: current.school || firstSchool }))
|
||||||
|
setEnrollmentForm((current) => ({
|
||||||
|
...current,
|
||||||
|
school: current.school || firstSchool,
|
||||||
|
student: current.student || firstStudent,
|
||||||
|
class: current.class || firstClass,
|
||||||
|
}))
|
||||||
|
} catch (loadError) {
|
||||||
|
const errorMessage = readableError(loadError)
|
||||||
|
console.error('Failed to load academic MVP data:', errorMessage)
|
||||||
|
setError(errorMessage)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentUser) return
|
||||||
|
|
||||||
|
loadAcademicData()
|
||||||
|
}, [currentUser])
|
||||||
|
|
||||||
|
const handleStudentField = (field: keyof StudentForm) => (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
||||||
|
) => {
|
||||||
|
setStudentForm((current) => ({ ...current, [field]: event.target.value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClassField = (field: keyof ClassForm) => (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
|
||||||
|
) => {
|
||||||
|
setClassForm((current) => ({ ...current, [field]: event.target.value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEnrollmentField = (field: keyof EnrollmentForm) => (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
|
||||||
|
) => {
|
||||||
|
setEnrollmentForm((current) => ({ ...current, [field]: event.target.value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateStudent = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setMessage('')
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (!studentForm.school) {
|
||||||
|
setError('Selecione uma escola antes de cadastrar o aluno.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!studentForm.nome_completo.trim()) {
|
||||||
|
setError('Informe o nome completo do aluno.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (studentForm.email.trim() && !studentForm.email.includes('@')) {
|
||||||
|
setError('Informe um email válido para o aluno.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSavingStudent(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('/students', { data: cleanStudentPayload(studentForm) })
|
||||||
|
setMessage('Aluno criado com sucesso. Agora pode ser matriculado numa turma.')
|
||||||
|
setStudentForm((current) => ({ ...emptyStudentForm, school: current.school }))
|
||||||
|
await loadAcademicData()
|
||||||
|
setActiveWorkflow('enrollment')
|
||||||
|
} catch (saveError) {
|
||||||
|
const errorMessage = readableError(saveError)
|
||||||
|
console.error('Failed to create student from Academic MVP:', errorMessage)
|
||||||
|
setError(errorMessage)
|
||||||
|
} finally {
|
||||||
|
setIsSavingStudent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateClass = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setMessage('')
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (!classForm.school) {
|
||||||
|
setError('Selecione uma escola antes de criar a turma.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!classForm.nome.trim()) {
|
||||||
|
setError('Informe o nome da turma.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSavingClass(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('/classes', { data: cleanClassPayload(classForm) })
|
||||||
|
setMessage('Turma criada com sucesso e pronta para receber matrículas.')
|
||||||
|
setClassForm((current) => ({ ...emptyClassForm, school: current.school }))
|
||||||
|
await loadAcademicData()
|
||||||
|
setActiveWorkflow('enrollment')
|
||||||
|
} catch (saveError) {
|
||||||
|
const errorMessage = readableError(saveError)
|
||||||
|
console.error('Failed to create class from Academic MVP:', errorMessage)
|
||||||
|
setError(errorMessage)
|
||||||
|
} finally {
|
||||||
|
setIsSavingClass(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateEnrollment = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setMessage('')
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (!enrollmentForm.school || !enrollmentForm.student || !enrollmentForm.class) {
|
||||||
|
setError('Selecione escola, aluno e turma para concluir a matrícula.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enrollmentForm.ano_lectivo.trim()) {
|
||||||
|
setError('Informe o ano lectivo da matrícula.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSavingEnrollment(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('/enrollments', { data: cleanEnrollmentPayload(enrollmentForm) })
|
||||||
|
setMessage('Matrícula criada com sucesso. O aluno já está associado à escola e turma.')
|
||||||
|
setEnrollmentForm((current) => ({ ...current, student: '', class: '', data_matricula: today }))
|
||||||
|
await loadAcademicData()
|
||||||
|
} catch (saveError) {
|
||||||
|
const errorMessage = readableError(saveError)
|
||||||
|
console.error('Failed to create enrollment from Academic MVP:', errorMessage)
|
||||||
|
setError(errorMessage)
|
||||||
|
} finally {
|
||||||
|
setIsSavingEnrollment(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('MVP Académico')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton icon={mdiBookEducationOutline} title="MVP Académico" main>
|
||||||
|
<BaseButton href="/students/students-list" color="whiteDark" label="CRUD académico" />
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
|
<div className="mb-6 overflow-hidden rounded-3xl bg-slate-950 text-white shadow-2xl shadow-slate-300/40 dark:shadow-none">
|
||||||
|
<div className="grid gap-8 bg-[radial-gradient(circle_at_top_left,_rgba(16,185,129,0.36),_transparent_34%),linear-gradient(135deg,#06281f_0%,#0f172a_52%,#1e3a8a_100%)] p-8 lg:grid-cols-[1.25fr_0.75fr]">
|
||||||
|
<div>
|
||||||
|
<span className="inline-flex rounded-full border border-emerald-300/30 bg-white/10 px-4 py-2 text-xs font-bold uppercase tracking-[0.24em] text-emerald-100">
|
||||||
|
GB-GESTÃO ESCOLAR S.A · Academic Core
|
||||||
|
</span>
|
||||||
|
<h2 className="mt-5 max-w-4xl text-3xl font-black tracking-tight sm:text-4xl lg:text-5xl">
|
||||||
|
Matrícula rápida, aluno, turma e operação escolar num único console.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-5 max-w-3xl text-base leading-7 text-slate-200">
|
||||||
|
Este MVP académico liga a escola/tenant ao ciclo operacional: cadastrar aluno, preparar turma, efetuar matrícula e acompanhar indicadores essenciais.
|
||||||
|
</p>
|
||||||
|
<div className="mt-7 flex flex-wrap gap-3">
|
||||||
|
<BaseButton color="success" label="Criar aluno" onClick={() => setActiveWorkflow('student')} />
|
||||||
|
<BaseButton color="info" label="Criar turma" onClick={() => setActiveWorkflow('class')} />
|
||||||
|
<BaseButton color="warning" label="Matricular" onClick={() => setActiveWorkflow('enrollment')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-1">
|
||||||
|
{[
|
||||||
|
['Escolas', counts.schools, mdiSchoolOutline],
|
||||||
|
['Alunos', counts.students, mdiAccountSchool],
|
||||||
|
['Turmas', counts.classes, mdiGoogleClassroom],
|
||||||
|
['Matrículas', counts.enrollments, mdiClipboardCheckOutline],
|
||||||
|
].map(([label, value, icon]) => (
|
||||||
|
<div key={label as string} className="rounded-3xl border border-white/10 bg-white/10 p-5 backdrop-blur">
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-slate-300">{label}</p>
|
||||||
|
<span className="rounded-2xl bg-emerald-400/20 p-2 text-emerald-100">
|
||||||
|
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-current" aria-hidden="true">
|
||||||
|
<path d={icon as string} />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-3xl font-black">{value as number}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(message || error) && (
|
||||||
|
<div className={`mb-6 rounded-3xl border p-4 text-sm font-semibold ${error ? 'border-rose-200 bg-rose-50 text-rose-700' : 'border-emerald-200 bg-emerald-50 text-emerald-700'}`}>
|
||||||
|
{error || message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-6 grid gap-4 md:grid-cols-3">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
key: 'student',
|
||||||
|
title: '1. Cadastrar aluno',
|
||||||
|
description: 'Dados mínimos do estudante para iniciar o processo académico.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'class',
|
||||||
|
title: '2. Preparar turma',
|
||||||
|
description: 'Turno, sala, capacidade e estado operacional da turma.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enrollment',
|
||||||
|
title: '3. Efetuar matrícula',
|
||||||
|
description: 'Ligação formal entre escola, aluno, turma e ano lectivo.',
|
||||||
|
},
|
||||||
|
].map((step) => (
|
||||||
|
<button
|
||||||
|
key={step.key}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setActiveWorkflow(step.key as 'student' | 'class' | 'enrollment')}
|
||||||
|
className={`rounded-3xl border p-5 text-left transition hover:-translate-y-0.5 hover:shadow-lg ${
|
||||||
|
activeWorkflow === step.key
|
||||||
|
? 'border-emerald-300 bg-emerald-50 shadow-lg shadow-emerald-100/70 dark:bg-emerald-950/30'
|
||||||
|
: 'border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="font-black text-slate-900 dark:text-white">{step.title}</p>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-slate-500 dark:text-slate-400">{step.description}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 xl:grid-cols-[1.05fr_0.95fr]">
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-emerald-600">Fluxo operacional</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">
|
||||||
|
{activeWorkflow === 'student' && 'Cadastro rápido de aluno'}
|
||||||
|
{activeWorkflow === 'class' && 'Criação rápida de turma'}
|
||||||
|
{activeWorkflow === 'enrollment' && 'Matrícula académica'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<BaseButton color="whiteDark" small icon={mdiRefresh} label={isLoading ? 'A carregar' : 'Atualizar'} onClick={loadAcademicData} disabled={isLoading} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{schools.length === 0 && !isLoading && (
|
||||||
|
<div className="mb-5 rounded-3xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-800">
|
||||||
|
Crie primeiro uma escola/tenant no Onboarding Multi-Escola para ativar o MVP académico.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeWorkflow === 'student' && (
|
||||||
|
<form onSubmit={handleCreateStudent} className="space-y-5">
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Escola
|
||||||
|
<select name="student_school" className={inputClass} value={studentForm.school} onChange={handleStudentField('school')}>
|
||||||
|
<option value="">Selecionar escola</option>
|
||||||
|
{schools.map((school) => (
|
||||||
|
<option key={school.id} value={school.id}>{recordLabel(school)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Nº de processo
|
||||||
|
<input name="numero_processo" className={inputClass} value={studentForm.numero_processo} onChange={handleStudentField('numero_processo')} placeholder="Ex.: GB-ALU-0001" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className={labelClass}>
|
||||||
|
Nome completo do aluno
|
||||||
|
<input name="nome_completo" className={inputClass} value={studentForm.nome_completo} onChange={handleStudentField('nome_completo')} placeholder="Nome completo" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Sexo
|
||||||
|
<select name="sexo" className={inputClass} value={studentForm.sexo} onChange={handleStudentField('sexo')}>
|
||||||
|
<option value="masculino">Masculino</option>
|
||||||
|
<option value="feminino">Feminino</option>
|
||||||
|
<option value="outro">Outro</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Data de nascimento
|
||||||
|
<input name="data_nascimento" className={inputClass} type="date" value={studentForm.data_nascimento} onChange={handleStudentField('data_nascimento')} />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
BI / Documento
|
||||||
|
<input name="bi" className={inputClass} value={studentForm.bi} onChange={handleStudentField('bi')} placeholder="BI, passaporte ou cédula" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Telefone
|
||||||
|
<input name="telefone" className={inputClass} value={studentForm.telefone} onChange={handleStudentField('telefone')} placeholder="+244 ..." />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Email
|
||||||
|
<input name="email" className={inputClass} type="email" value={studentForm.email} onChange={handleStudentField('email')} placeholder="aluno@escola.ao" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className={labelClass}>
|
||||||
|
Morada
|
||||||
|
<textarea name="morada" className={`${inputClass} min-h-24`} value={studentForm.morada} onChange={handleStudentField('morada')} placeholder="Endereço do aluno" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<BaseButton type="submit" color="success" label={isSavingStudent ? 'A criar aluno...' : 'Criar aluno'} disabled={!canCreateStudents || isSavingStudent} />
|
||||||
|
{!canCreateStudents && <p className="text-sm text-amber-600">O utilizador atual não tem permissão CREATE_STUDENTS.</p>}
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeWorkflow === 'class' && (
|
||||||
|
<form onSubmit={handleCreateClass} className="space-y-5">
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Escola
|
||||||
|
<select name="class_school" className={inputClass} value={classForm.school} onChange={handleClassField('school')}>
|
||||||
|
<option value="">Selecionar escola</option>
|
||||||
|
{schools.map((school) => (
|
||||||
|
<option key={school.id} value={school.id}>{recordLabel(school)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Nome da turma
|
||||||
|
<input name="class_nome" className={inputClass} value={classForm.nome} onChange={handleClassField('nome')} placeholder="Ex.: 10ª Classe A" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Turno
|
||||||
|
<select name="turno" className={inputClass} value={classForm.turno} onChange={handleClassField('turno')}>
|
||||||
|
<option value="manha">Manhã</option>
|
||||||
|
<option value="tarde">Tarde</option>
|
||||||
|
<option value="noite">Noite</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Sala
|
||||||
|
<input name="sala" className={inputClass} value={classForm.sala} onChange={handleClassField('sala')} placeholder="Ex.: Sala 12" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Capacidade
|
||||||
|
<input name="capacidade" className={inputClass} type="number" min="1" value={classForm.capacidade} onChange={handleClassField('capacidade')} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BaseButton type="submit" color="info" label={isSavingClass ? 'A criar turma...' : 'Criar turma'} disabled={!canCreateClasses || isSavingClass} />
|
||||||
|
{!canCreateClasses && <p className="text-sm text-amber-600">O utilizador atual não tem permissão CREATE_CLASSES.</p>}
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeWorkflow === 'enrollment' && (
|
||||||
|
<form onSubmit={handleCreateEnrollment} className="space-y-5">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Escola
|
||||||
|
<select name="enrollment_school" className={inputClass} value={enrollmentForm.school} onChange={handleEnrollmentField('school')}>
|
||||||
|
<option value="">Selecionar escola</option>
|
||||||
|
{schools.map((school) => (
|
||||||
|
<option key={school.id} value={school.id}>{recordLabel(school)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Aluno
|
||||||
|
<select name="enrollment_student" className={inputClass} value={enrollmentForm.student} onChange={handleEnrollmentField('student')}>
|
||||||
|
<option value="">Selecionar aluno</option>
|
||||||
|
{students.map((student) => (
|
||||||
|
<option key={student.id} value={student.id}>{recordLabel(student)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Turma
|
||||||
|
<select name="enrollment_class" className={inputClass} value={enrollmentForm.class} onChange={handleEnrollmentField('class')}>
|
||||||
|
<option value="">Selecionar turma</option>
|
||||||
|
{classes.map((classItem) => (
|
||||||
|
<option key={classItem.id} value={classItem.id}>{recordLabel(classItem)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Ano lectivo
|
||||||
|
<input name="ano_lectivo" className={inputClass} value={enrollmentForm.ano_lectivo} onChange={handleEnrollmentField('ano_lectivo')} placeholder="2026" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Data da matrícula
|
||||||
|
<input name="data_matricula" className={inputClass} type="date" value={enrollmentForm.data_matricula} onChange={handleEnrollmentField('data_matricula')} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BaseButton type="submit" color="warning" label={isSavingEnrollment ? 'A matricular...' : 'Criar matrícula'} disabled={!canCreateEnrollments || isSavingEnrollment} />
|
||||||
|
{!canCreateEnrollments && <p className="text-sm text-amber-600">O utilizador atual não tem permissão CREATE_ENROLLMENTS.</p>}
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-5">
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-blue-600">BI académico inicial</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">Saúde operacional</h3>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-3 xl:grid-cols-1 2xl:grid-cols-3">
|
||||||
|
{[
|
||||||
|
['Matrículas ativas', activeEnrollments],
|
||||||
|
['Média por turma', occupancy],
|
||||||
|
['Ano lectivo', currentAcademicYear],
|
||||||
|
].map(([label, value]) => (
|
||||||
|
<div key={label as string} className="rounded-3xl bg-slate-50 p-5 dark:bg-slate-900">
|
||||||
|
<p className="text-xs font-bold uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
||||||
|
<p className="mt-2 text-2xl font-black text-slate-900 dark:text-white">{value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-5 flex items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-emerald-600">Últimas matrículas</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">Operação recente</h3>
|
||||||
|
</div>
|
||||||
|
<BaseButton href="/enrollments/enrollments-list" color="whiteDark" small label="Ver todas" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading && <p className="rounded-2xl bg-slate-50 p-5 text-sm text-slate-500">A carregar dados académicos...</p>}
|
||||||
|
|
||||||
|
{!isLoading && recentEnrollments.length === 0 && (
|
||||||
|
<div className="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-8 text-center dark:border-slate-700 dark:bg-slate-900">
|
||||||
|
<p className="text-lg font-black text-slate-800 dark:text-white">Ainda não há matrículas.</p>
|
||||||
|
<p className="mt-2 text-sm text-slate-500">Use o fluxo à esquerda para criar a primeira matrícula académica.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{recentEnrollments.map((enrollment) => (
|
||||||
|
<div key={enrollment.id} className="rounded-3xl border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="font-black text-slate-900 dark:text-white">{recordLabel(enrollment.student)}</p>
|
||||||
|
<p className="mt-1 text-sm text-slate-500">
|
||||||
|
{recordLabel(enrollment.class)} · {recordLabel(enrollment.school)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-emerald-50 px-3 py-1 text-xs font-bold text-emerald-700 ring-1 ring-emerald-200">
|
||||||
|
{enrollment.status || 'ativa'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||||
|
Ano lectivo {enrollment.ano_lectivo || '—'} · {enrollment.data_matricula ? new Date(enrollment.data_matricula).toLocaleDateString('pt-PT') : 'sem data'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid gap-4 md:grid-cols-4">
|
||||||
|
{[
|
||||||
|
['Frequência', '/attendance/attendance-list'],
|
||||||
|
['Avaliações', '/assessments/assessments-list'],
|
||||||
|
['Notas/Pautas', '/grades/grades-list'],
|
||||||
|
['Disciplinas', '/subjects/subjects-list'],
|
||||||
|
].map(([label, href]) => (
|
||||||
|
<CardBox key={label} className="border-0 shadow-lg shadow-slate-200/60 dark:shadow-none">
|
||||||
|
<p className="font-black text-slate-900 dark:text-white">{label}</p>
|
||||||
|
<p className="mt-2 min-h-12 text-sm leading-6 text-slate-500">Módulo já preparado no CRUD para expansão do MVP académico.</p>
|
||||||
|
<BaseButton href={href} color="whiteDark" small label="Abrir" />
|
||||||
|
</CardBox>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AcademicMvpPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <LayoutAuthenticated permission="READ_STUDENTS">{page}</LayoutAuthenticated>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AcademicMvpPage
|
||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/assessments/assessmentsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/assessments/assessmentsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const AssessmentsTablesPage = () => {
|
const AssessmentsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.assessments', 'Assessments');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -100,28 +113,28 @@ const AssessmentsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Assessments')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Assessments" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/assessments/assessments-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/assessments/assessments-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getAssessmentsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getAssessmentsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -143,9 +156,9 @@ const AssessmentsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/attendance/attendanceSlice';
|
import {setRefetch, uploadCsv} from '../../stores/attendance/attendanceSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const AttendanceTablesPage = () => {
|
const AttendanceTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.attendance', 'Attendance');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -92,28 +105,28 @@ const AttendanceTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Attendance')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Attendance" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/attendance/attendance-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/attendance/attendance-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getAttendanceCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getAttendanceCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const AttendanceTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/book_loans/book_loansSlice';
|
import {setRefetch, uploadCsv} from '../../stores/book_loans/book_loansSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Book_loansTablesPage = () => {
|
const Book_loansTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.bookLoans', 'Book_loans');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -96,28 +109,28 @@ const Book_loansTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Book_loans')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Book_loans" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/book_loans/book_loans-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/book_loans/book_loans-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getBook_loansCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getBook_loansCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -143,9 +156,9 @@ const Book_loansTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/books/booksSlice';
|
import {setRefetch, uploadCsv} from '../../stores/books/booksSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const BooksTablesPage = () => {
|
const BooksTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.books', 'Books');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const BooksTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Books')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Books" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/books/books-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/books/books-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getBooksCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getBooksCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const BooksTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/classes/classesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/classes/classesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ClassesTablesPage = () => {
|
const ClassesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.classes', 'Classes');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -92,28 +105,28 @@ const ClassesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Classes')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Classes" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/classes/classes-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/classes/classes-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getClassesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getClassesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const ClassesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/courses/coursesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/courses/coursesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const CoursesTablesPage = () => {
|
const CoursesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.courses', 'Courses');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const CoursesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Courses')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Courses" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/courses/courses-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/courses/courses-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getCoursesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getCoursesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const CoursesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -16,13 +16,19 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
|||||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||||
|
|
||||||
const loadingMessage = 'Loading...';
|
const loadingMessage = translate('pages.dashboard.loading', 'Loading...');
|
||||||
|
|
||||||
|
|
||||||
const [users, setUsers] = React.useState(loadingMessage);
|
const [users, setUsers] = React.useState(loadingMessage);
|
||||||
@ -88,6 +94,10 @@ const Dashboard = () => {
|
|||||||
async function getWidgets(roleId) {
|
async function getWidgets(roleId) {
|
||||||
await dispatch(fetchWidgets(roleId));
|
await dispatch(fetchWidgets(roleId));
|
||||||
}
|
}
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
loadData().then();
|
loadData().then();
|
||||||
@ -103,13 +113,13 @@ const Dashboard = () => {
|
|||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
{getPageTitle('Overview')}
|
{getPageTitle(translate('pages.dashboard.overview', 'Overview'))}
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton
|
<SectionTitleLineWithButton
|
||||||
icon={icon.mdiChartTimelineVariant}
|
icon={icon.mdiChartTimelineVariant}
|
||||||
title='Overview'
|
title={translate('pages.dashboard.overview', 'Overview')}
|
||||||
main>
|
main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
@ -123,7 +133,7 @@ const Dashboard = () => {
|
|||||||
{!!rolesWidgets.length &&
|
{!!rolesWidgets.length &&
|
||||||
hasPermission(currentUser, 'CREATE_ROLES') && (
|
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||||
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
||||||
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
{translate('pages.dashboard.roleWidgets', '{{role}}\'s widgets').replace('{{role}}', widgetsRole?.role?.label || translate('entities.users', 'Users'))}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -137,7 +147,7 @@ const Dashboard = () => {
|
|||||||
size={48}
|
size={48}
|
||||||
path={icon.mdiLoading}
|
path={icon.mdiLoading}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
Loading widgets...
|
{translate('pages.dashboard.loadingWidgets', 'Loading widgets...')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -165,7 +175,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Users
|
{translate('entities.users', 'Users')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{users}
|
{users}
|
||||||
@ -193,7 +203,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Roles
|
{translate('entities.roles', 'Roles')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{roles}
|
{roles}
|
||||||
@ -221,7 +231,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Permissions
|
{translate('entities.permissions', 'Permissions')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{permissions}
|
{permissions}
|
||||||
@ -249,7 +259,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Schools
|
{translate('entities.schools', 'Schools')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{schools}
|
{schools}
|
||||||
@ -277,7 +287,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Students
|
{translate('entities.students', 'Students')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{students}
|
{students}
|
||||||
@ -305,7 +315,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Guardians
|
{translate('entities.guardians', 'Guardians')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{guardians}
|
{guardians}
|
||||||
@ -333,7 +343,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Student guardians
|
{translate('entities.studentGuardians', 'Student guardians')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{student_guardians}
|
{student_guardians}
|
||||||
@ -361,7 +371,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Teachers
|
{translate('entities.teachers', 'Teachers')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{teachers}
|
{teachers}
|
||||||
@ -389,7 +399,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Courses
|
{translate('entities.courses', 'Courses')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{courses}
|
{courses}
|
||||||
@ -417,7 +427,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Grades
|
{translate('entities.grades', 'Grades')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{grades}
|
{grades}
|
||||||
@ -445,7 +455,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Classes
|
{translate('entities.classes', 'Classes')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{classes}
|
{classes}
|
||||||
@ -473,7 +483,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Subjects
|
{translate('entities.subjects', 'Subjects')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{subjects}
|
{subjects}
|
||||||
@ -501,7 +511,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Enrollments
|
{translate('entities.enrollments', 'Enrollments')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{enrollments}
|
{enrollments}
|
||||||
@ -529,7 +539,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Assessments
|
{translate('entities.assessments', 'Assessments')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{assessments}
|
{assessments}
|
||||||
@ -557,7 +567,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Attendance
|
{translate('entities.attendance', 'Attendance')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{attendance}
|
{attendance}
|
||||||
@ -585,7 +595,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Invoices
|
{translate('entities.invoices', 'Invoices')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{invoices}
|
{invoices}
|
||||||
@ -613,7 +623,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Payments
|
{translate('entities.payments', 'Payments')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{payments}
|
{payments}
|
||||||
@ -641,7 +651,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Employees
|
{translate('entities.employees', 'Employees')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{employees}
|
{employees}
|
||||||
@ -669,7 +679,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Products
|
{translate('entities.products', 'Products')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{products}
|
{products}
|
||||||
@ -697,7 +707,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Books
|
{translate('entities.books', 'Books')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{books}
|
{books}
|
||||||
@ -725,7 +735,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Book loans
|
{translate('entities.bookLoans', 'Book loans')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{book_loans}
|
{book_loans}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/employees/employeesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/employees/employeesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const EmployeesTablesPage = () => {
|
const EmployeesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.employees', 'Employees');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const EmployeesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Employees')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Employees" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/employees/employees-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/employees/employees-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getEmployeesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getEmployeesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const EmployeesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/enrollments/enrollmentsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/enrollments/enrollmentsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const EnrollmentsTablesPage = () => {
|
const EnrollmentsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.enrollments', 'Enrollments');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -96,28 +109,28 @@ const EnrollmentsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Enrollments')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Enrollments" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/enrollments/enrollments-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/enrollments/enrollments-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getEnrollmentsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getEnrollmentsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -139,9 +152,9 @@ const EnrollmentsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -13,32 +13,42 @@ import BaseButtons from '../components/BaseButtons';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function Forgot() {
|
export default function Forgot() {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(false)
|
const [loading, setLoading] = React.useState(false)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notify = (type, msg) => toast( msg, {type});
|
const notify = (type, msg) => toast( msg, {type});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = async (value) => {
|
const handleSubmit = async (value) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const { data: response } = await axios.post('/auth/send-password-reset-email', value);
|
const { data: response } = await axios.post('/auth/send-password-reset-email', value);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
notify('success', 'Please check your email for verification link');
|
notify('success', translate('pages.auth.checkEmailVerification', 'Please check your email for verification link'));
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await router.push('/login')
|
await router.push('/login')
|
||||||
}, 3000)
|
}, 3000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
console.log('error: ', error)
|
console.log('error: ', error)
|
||||||
notify('error', 'Something was wrong. Try again')
|
notify('error', translate('pages.auth.genericError', 'Something was wrong. Try again'))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Login')}</title>
|
<title>{getPageTitle(translate('pages.forgot.pageTitle', 'Forgot password'))}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
@ -50,7 +60,7 @@ export default function Forgot() {
|
|||||||
onSubmit={(values) => handleSubmit(values)}
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormField label='Email' help='Please enter your email'>
|
<FormField label={translate('pages.auth.emailLabel', 'Email')} help={translate('pages.auth.emailHelp', 'Please enter your email')}>
|
||||||
<Field name='email' />
|
<Field name='email' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -59,12 +69,12 @@ export default function Forgot() {
|
|||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
type='submit'
|
type='submit'
|
||||||
label={loading ? 'Loading...' : 'Submit' }
|
label={loading ? translate('pages.auth.loading', 'Loading...') : translate('pages.forgot.submit', 'Submit')}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
href={'/login'}
|
href={'/login'}
|
||||||
label={'Login'}
|
label={translate('pages.auth.login', 'Login')}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/grades/gradesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/grades/gradesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const GradesTablesPage = () => {
|
const GradesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.grades', 'Grades');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const GradesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Grades')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Grades" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/grades/grades-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/grades/grades-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getGradesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getGradesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const GradesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/guardians/guardiansSlice';
|
import {setRefetch, uploadCsv} from '../../stores/guardians/guardiansSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const GuardiansTablesPage = () => {
|
const GuardiansTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.guardians', 'Guardians');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const GuardiansTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Guardians')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Guardians" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/guardians/guardians-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/guardians/guardians-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getGuardiansCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getGuardiansCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const GuardiansTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -1,166 +1,175 @@
|
|||||||
|
import { mdiArrowRight, mdiChartTimelineVariant, mdiLockCheckOutline, mdiSchoolOutline } from '@mdi/js'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import BaseButton from '../components/BaseButton'
|
||||||
import type { ReactElement } from 'react';
|
import BaseIcon from '../components/BaseIcon'
|
||||||
import Head from 'next/head';
|
import LayoutGuest from '../layouts/Guest'
|
||||||
import Link from 'next/link';
|
import { getPageTitle } from '../config'
|
||||||
import BaseButton from '../components/BaseButton';
|
|
||||||
import CardBox from '../components/CardBox';
|
|
||||||
import SectionFullScreen from '../components/SectionFullScreen';
|
|
||||||
import LayoutGuest from '../layouts/Guest';
|
|
||||||
import BaseDivider from '../components/BaseDivider';
|
|
||||||
import BaseButtons from '../components/BaseButtons';
|
|
||||||
import { getPageTitle } from '../config';
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
|
||||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
|
||||||
|
|
||||||
|
const modules = [
|
||||||
|
'Gestão Escolar',
|
||||||
|
'Financeiro',
|
||||||
|
'RH',
|
||||||
|
'Biblioteca',
|
||||||
|
'Stock',
|
||||||
|
'Portais',
|
||||||
|
'BI',
|
||||||
|
'IA',
|
||||||
|
]
|
||||||
|
|
||||||
|
const metrics = [
|
||||||
|
['Multi-escola', 'Dados isolados por escola'],
|
||||||
|
['PALOP ready', 'Pensado para Angola e expansão'],
|
||||||
|
['ERP modular', 'Cresce por fases sem refazer a base'],
|
||||||
|
]
|
||||||
|
|
||||||
export default function Starter() {
|
export default function Starter() {
|
||||||
const [illustrationImage, setIllustrationImage] = useState({
|
|
||||||
src: undefined,
|
|
||||||
photographer: undefined,
|
|
||||||
photographer_url: undefined,
|
|
||||||
})
|
|
||||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
|
||||||
const [contentType, setContentType] = useState('image');
|
|
||||||
const [contentPosition, setContentPosition] = useState('right');
|
|
||||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
|
||||||
|
|
||||||
const title = 'GB Gestao Escolar SA'
|
|
||||||
|
|
||||||
// Fetch Pexels image/video
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchData() {
|
|
||||||
const image = await getPexelsImage();
|
|
||||||
const video = await getPexelsVideo();
|
|
||||||
setIllustrationImage(image);
|
|
||||||
setIllustrationVideo(video);
|
|
||||||
}
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const imageBlock = (image) => (
|
|
||||||
<div
|
|
||||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
|
||||||
style={{
|
|
||||||
backgroundImage: `${
|
|
||||||
image
|
|
||||||
? `url(${image?.src?.original})`
|
|
||||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
|
||||||
}`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'left center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
|
||||||
<a
|
|
||||||
className='text-[8px]'
|
|
||||||
href={image?.photographer_url}
|
|
||||||
target='_blank'
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
Photo by {image?.photographer} on Pexels
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const videoBlock = (video) => {
|
|
||||||
if (video?.video_files?.length > 0) {
|
|
||||||
return (
|
|
||||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
|
||||||
<video
|
|
||||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
|
||||||
autoPlay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
>
|
|
||||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
|
||||||
<a
|
|
||||||
className='text-[8px]'
|
|
||||||
href={video?.user?.url}
|
|
||||||
target='_blank'
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
Video by {video.user.name} on Pexels
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
style={
|
|
||||||
contentPosition === 'background'
|
|
||||||
? {
|
|
||||||
backgroundImage: `${
|
|
||||||
illustrationImage
|
|
||||||
? `url(${illustrationImage.src?.original})`
|
|
||||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
|
||||||
}`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'left center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Starter Page')}</title>
|
<title>{getPageTitle('GB-GESTÃO ESCOLAR S.A')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<main className="min-h-screen overflow-hidden bg-[#F8FAFC] text-slate-950">
|
||||||
<div
|
<nav className="mx-auto flex max-w-7xl items-center justify-between px-6 py-6">
|
||||||
className={`flex ${
|
<Link href="/" className="flex items-center gap-3">
|
||||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
<span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-[#073B2A] text-sm font-black text-white shadow-lg shadow-emerald-900/20">
|
||||||
} min-h-screen w-full`}
|
GB
|
||||||
>
|
</span>
|
||||||
{contentType === 'image' && contentPosition !== 'background'
|
<span>
|
||||||
? imageBlock(illustrationImage)
|
<span className="block text-sm font-black tracking-tight">GB-GESTÃO ESCOLAR</span>
|
||||||
: null}
|
<span className="block text-xs font-semibold text-slate-500">SaaS multi-escola</span>
|
||||||
{contentType === 'video' && contentPosition !== 'background'
|
</span>
|
||||||
? videoBlock(illustrationVideo)
|
</Link>
|
||||||
: null}
|
<div className="flex items-center gap-3">
|
||||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
<Link href="/login" className="hidden rounded-full px-4 py-2 text-sm font-bold text-slate-700 transition hover:bg-white hover:text-slate-950 sm:inline-flex">
|
||||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
Login
|
||||||
<CardBoxComponentTitle title="Welcome to your GB Gestao Escolar SA app!"/>
|
</Link>
|
||||||
|
<BaseButton href="/login" color="info" label="Admin interface" roundedFull />
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<section className="relative mx-auto grid max-w-7xl gap-10 px-6 pb-20 pt-8 lg:grid-cols-[1.05fr_0.95fr] lg:items-center lg:pt-14">
|
||||||
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
<div className="absolute left-1/2 top-24 -z-0 h-72 w-72 rounded-full bg-emerald-300/30 blur-3xl" />
|
||||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
<div className="relative z-10">
|
||||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
<span className="inline-flex rounded-full border border-emerald-200 bg-white px-4 py-2 text-xs font-black uppercase tracking-[0.24em] text-[#087A52] shadow-sm">
|
||||||
|
Angola · PALOP · ERP Escolar
|
||||||
|
</span>
|
||||||
|
<h1 className="mt-7 max-w-4xl text-5xl font-black leading-[0.95] tracking-[-0.05em] text-slate-950 md:text-7xl">
|
||||||
|
Uma plataforma única para gerir várias escolas com dados isolados.
|
||||||
|
</h1>
|
||||||
|
<p className="mt-6 max-w-2xl text-lg leading-8 text-slate-600">
|
||||||
|
O GB-GESTÃO ESCOLAR S.A organiza escolas, alunos, professores, matrículas, finanças e operações num SaaS moderno preparado para crescer de uma instituição para uma rede completa.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
||||||
|
<BaseButton href="/login" color="info" label="Entrar no admin" roundedFull className="shadow-xl shadow-emerald-900/20" />
|
||||||
|
<BaseButton href="/login" color="whiteDark" outline label="Criar primeira escola" roundedFull />
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 grid gap-4 sm:grid-cols-3">
|
||||||
|
{metrics.map(([title, subtitle]) => (
|
||||||
|
<div key={title} className="rounded-3xl border border-white bg-white/80 p-5 shadow-xl shadow-slate-200/60 backdrop-blur">
|
||||||
|
<p className="font-black text-slate-950">{title}</p>
|
||||||
|
<p className="mt-2 text-sm text-slate-500">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="rounded-[2rem] bg-[#071B2D] p-3 shadow-2xl shadow-blue-950/30">
|
||||||
|
<div className="rounded-[1.5rem] bg-[radial-gradient(circle_at_top_left,_rgba(16,185,129,0.35),_transparent_32%),linear-gradient(135deg,#0f172a_0%,#082f49_55%,#064e3b_100%)] p-6 text-white">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-emerald-100">Executive dashboard</p>
|
||||||
|
<p className="mt-1 text-2xl font-black">Rede Escolar</p>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-emerald-400/20 px-3 py-1 text-xs font-bold text-emerald-100 ring-1 ring-emerald-200/20">
|
||||||
|
Online + Offline ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid gap-4 sm:grid-cols-2">
|
||||||
|
{[
|
||||||
|
['12', 'Escolas'],
|
||||||
|
['8.420', 'Alunos'],
|
||||||
|
['AOA 31M', 'Receita mensal'],
|
||||||
|
['94%', 'Frequência'],
|
||||||
|
].map(([value, label]) => (
|
||||||
|
<div key={label} className="rounded-3xl border border-white/10 bg-white/10 p-5 backdrop-blur">
|
||||||
|
<p className="text-3xl font-black">{value}</p>
|
||||||
|
<p className="mt-2 text-sm text-slate-300">{label}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 rounded-3xl border border-white/10 bg-white p-5 text-slate-950">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-emerald-50 text-emerald-700">
|
||||||
|
<BaseIcon path={mdiSchoolOutline} size={22} />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<p className="font-black">Colégio Esperança</p>
|
||||||
|
<p className="text-sm text-slate-500">Tenant ativo · school_id isolado</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 h-3 rounded-full bg-slate-100">
|
||||||
|
<div className="h-3 w-4/5 rounded-full bg-gradient-to-r from-emerald-500 to-blue-600" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="bg-white px-6 py-20">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="grid gap-8 lg:grid-cols-[0.8fr_1.2fr] lg:items-end">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-black uppercase tracking-[0.24em] text-emerald-600">MVP profissional</p>
|
||||||
|
<h2 className="mt-3 text-4xl font-black tracking-tight text-slate-950">Do cadastro da escola ao crescimento modular.</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-lg leading-8 text-slate-600">
|
||||||
|
A primeira entrega foca no fluxo central de SaaS: criar e visualizar escolas/tenants. Depois disso, alunos, professores, matrículas e financeiro passam a operar ligados à escola correta.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BaseButtons>
|
<div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<BaseButton
|
{modules.map((module, index) => (
|
||||||
href='/login'
|
<div key={module} className="group rounded-3xl border border-slate-100 bg-slate-50 p-6 transition hover:-translate-y-1 hover:bg-slate-950 hover:text-white hover:shadow-2xl hover:shadow-slate-300/70">
|
||||||
label='Login'
|
<div className="mb-8 flex items-center justify-between">
|
||||||
color='info'
|
<span className="text-sm font-black text-emerald-600 group-hover:text-emerald-300">0{index + 1}</span>
|
||||||
className='w-full'
|
<BaseIcon path={index % 2 ? mdiLockCheckOutline : mdiChartTimelineVariant} size={22} />
|
||||||
/>
|
</div>
|
||||||
|
<p className="text-lg font-black">{module}</p>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-slate-500 group-hover:text-slate-300">Preparado para ser ativado por fases com permissões e dados por escola.</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</BaseButtons>
|
<section className="px-6 py-16">
|
||||||
</CardBox>
|
<div className="mx-auto flex max-w-7xl flex-col items-start justify-between gap-6 rounded-[2rem] bg-[#073B2A] p-8 text-white shadow-2xl shadow-emerald-900/20 md:flex-row md:items-center">
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<p className="text-sm font-bold uppercase tracking-[0.24em] text-emerald-200">Admin interface</p>
|
||||||
</SectionFullScreen>
|
<h2 className="mt-2 text-3xl font-black">Entre para criar a primeira escola tenant.</h2>
|
||||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
</div>
|
||||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
<Link href="/login" className="inline-flex items-center gap-2 rounded-full bg-white px-6 py-3 text-sm font-black text-[#073B2A] transition hover:bg-emerald-50">
|
||||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
Abrir painel <BaseIcon path={mdiArrowRight} size={18} />
|
||||||
Privacy Policy
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
</div>
|
<footer className="border-t border-slate-200 px-6 py-8 text-center text-sm text-slate-500">
|
||||||
);
|
© 2026 GB-GESTÃO ESCOLAR S.A · <Link href="/privacy-policy" className="font-bold text-slate-700">Privacy Policy</Link>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <LayoutGuest>{page}</LayoutGuest>;
|
return <LayoutGuest>{page}</LayoutGuest>
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/invoices/invoicesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/invoices/invoicesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const InvoicesTablesPage = () => {
|
const InvoicesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.invoices', 'Invoices');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -92,28 +105,28 @@ const InvoicesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Invoices')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Invoices" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/invoices/invoices-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/invoices/invoices-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getInvoicesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getInvoicesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -137,9 +150,9 @@ const InvoicesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -21,9 +21,11 @@ import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {toast, ToastContainer} from "react-toastify";
|
import {toast, ToastContainer} from "react-toastify";
|
||||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'
|
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation('common');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||||
@ -37,6 +39,7 @@ export default function Login() {
|
|||||||
const [contentType, setContentType] = useState('image');
|
const [contentType, setContentType] = useState('image');
|
||||||
const [contentPosition, setContentPosition] = useState('right');
|
const [contentPosition, setContentPosition] = useState('right');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const { currentUser, isFetching, errorMessage, token, notify:notifyState } = useAppSelector(
|
const { currentUser, isFetching, errorMessage, token, notify:notifyState } = useAppSelector(
|
||||||
(state) => state.auth,
|
(state) => state.auth,
|
||||||
);
|
);
|
||||||
@ -46,6 +49,10 @@ export default function Login() {
|
|||||||
|
|
||||||
const title = 'GB Gestao Escolar SA'
|
const title = 'GB Gestao Escolar SA'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Fetch Pexels image/video
|
// Fetch Pexels image/video
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
@ -100,20 +107,30 @@ export default function Login() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageBlock = (image) => (
|
const imageBlock = (image) => {
|
||||||
<div className="hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3"
|
const imageUrl = image?.src?.original;
|
||||||
style={{
|
const photoCredit = t('pages.login.pexels.photoCredit', { photographer: image?.photographer || 'Pexels' });
|
||||||
backgroundImage: `${image ? `url(${image.src?.original})` : 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'}`,
|
|
||||||
backgroundSize: 'cover',
|
return (
|
||||||
backgroundPosition: 'left center',
|
<div className="hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3"
|
||||||
backgroundRepeat: 'no-repeat',
|
style={{
|
||||||
}}>
|
backgroundImage: imageUrl ? `url(${imageUrl})` : 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))',
|
||||||
<div className="flex justify-center w-full bg-blue-300/20">
|
backgroundSize: 'cover',
|
||||||
<a className="text-[8px]" href={image?.photographer_url} target="_blank" rel="noreferrer">Photo
|
backgroundPosition: 'left center',
|
||||||
by {image?.photographer} on Pexels</a>
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}}>
|
||||||
|
<div className="flex justify-center w-full bg-blue-300/20">
|
||||||
|
{image?.photographer_url ? (
|
||||||
|
<a className="text-[8px]" href={image.photographer_url} target="_blank" rel="noreferrer">
|
||||||
|
{photoCredit}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-[8px]">{photoCredit}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
)
|
};
|
||||||
|
|
||||||
const videoBlock = (video) => {
|
const videoBlock = (video) => {
|
||||||
if (video?.video_files?.length > 0) {
|
if (video?.video_files?.length > 0) {
|
||||||
@ -126,7 +143,7 @@ export default function Login() {
|
|||||||
muted
|
muted
|
||||||
>
|
>
|
||||||
<source src={video.video_files[0]?.link} type='video/mp4'/>
|
<source src={video.video_files[0]?.link} type='video/mp4'/>
|
||||||
Your browser does not support the video tag.
|
{t('pages.login.pexels.videoUnsupported')}
|
||||||
</video>
|
</video>
|
||||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||||
<a
|
<a
|
||||||
@ -135,18 +152,22 @@ export default function Login() {
|
|||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noreferrer'
|
rel='noreferrer'
|
||||||
>
|
>
|
||||||
Video by {video.user.name} on Pexels
|
{t('pages.login.pexels.videoCredit', { name: video.user.name })}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={contentPosition === 'background' ? {
|
<div style={contentPosition === 'background' ? {
|
||||||
backgroundImage: `${
|
backgroundImage: `${
|
||||||
illustrationImage
|
illustrationImage?.src?.original
|
||||||
? `url(${illustrationImage.src?.original})`
|
? `url(${illustrationImage.src.original})`
|
||||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||||
}`,
|
}`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
@ -154,7 +175,7 @@ export default function Login() {
|
|||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
} : {}}>
|
} : {}}>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Login')}</title>
|
<title>{getPageTitle(t('pages.login.pageTitle'))}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
@ -165,30 +186,36 @@ export default function Login() {
|
|||||||
|
|
||||||
<CardBox id="loginRoles" className='w-full md:w-3/5 lg:w-2/3'>
|
<CardBox id="loginRoles" className='w-full md:w-3/5 lg:w-2/3'>
|
||||||
|
|
||||||
<h2 className="text-4xl font-semibold my-4">{title}</h2>
|
<div className='my-4'>
|
||||||
|
<h2 className="text-4xl font-semibold">{title}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-row text-gray-500 justify-between'>
|
<div className='flex flex-row text-gray-500 justify-between'>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<p className='mb-2'>Use{' '}
|
<p className='mb-2'>
|
||||||
|
{t('pages.login.sampleCredentialsSuperAdmin', { email: '', password: '' })}{' '}
|
||||||
<code className={`cursor-pointer ${textColor} `}
|
<code className={`cursor-pointer ${textColor} `}
|
||||||
data-password="1561e4f2"
|
data-password="1561e4f2"
|
||||||
onClick={(e) => setLogin(e.target)}>super_admin@flatlogic.com</code>{' / '}
|
onClick={(e) => setLogin(e.target)}>super_admin@flatlogic.com</code>{' / '}
|
||||||
<code className={`${textColor}`}>1561e4f2</code>{' / '}
|
<code className={`${textColor}`}>1561e4f2</code>
|
||||||
to login as Super Admin</p>
|
</p>
|
||||||
|
|
||||||
<p className='mb-2'>Use{' '}
|
<p className='mb-2'>
|
||||||
|
{t('pages.login.sampleCredentialsAdmin', { email: '', password: '' })}{' '}
|
||||||
<code className={`cursor-pointer ${textColor} `}
|
<code className={`cursor-pointer ${textColor} `}
|
||||||
data-password="1561e4f2"
|
data-password="1561e4f2"
|
||||||
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
|
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
|
||||||
<code className={`${textColor}`}>1561e4f2</code>{' / '}
|
<code className={`${textColor}`}>1561e4f2</code>
|
||||||
to login as Admin</p>
|
</p>
|
||||||
<p>Use <code
|
<p>
|
||||||
className={`cursor-pointer ${textColor} `}
|
{t('pages.login.sampleCredentialsUser', { email: '', password: '' })}{' '}
|
||||||
data-password="ba8575c6f095"
|
<code
|
||||||
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
className={`cursor-pointer ${textColor} `}
|
||||||
<code className={`${textColor}`}>ba8575c6f095</code>{' / '}
|
data-password="ba8575c6f095"
|
||||||
to login as User</p>
|
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
||||||
|
<code className={`${textColor}`}>ba8575c6f095</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<BaseIcon
|
<BaseIcon
|
||||||
@ -210,16 +237,18 @@ export default function Login() {
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormField
|
<FormField
|
||||||
label='Login'
|
label={t('pages.login.form.loginLabel')}
|
||||||
help='Please enter your login'>
|
labelFor='email'
|
||||||
<Field name='email' />
|
help={t('pages.login.form.loginHelp')}>
|
||||||
|
<Field id='email' name='email' autoComplete='username' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<FormField
|
<FormField
|
||||||
label='Password'
|
label={t('pages.login.form.passwordLabel')}
|
||||||
help='Please enter your password'>
|
labelFor='password'
|
||||||
<Field name='password' type={showPassword ? 'text' : 'password'} />
|
help={t('pages.login.form.passwordHelp')}>
|
||||||
|
<Field id='password' name='password' type={showPassword ? 'text' : 'password'} autoComplete='current-password' />
|
||||||
</FormField>
|
</FormField>
|
||||||
<div
|
<div
|
||||||
className='absolute bottom-8 right-0 pr-3 flex items-center cursor-pointer'
|
className='absolute bottom-8 right-0 pr-3 flex items-center cursor-pointer'
|
||||||
@ -234,12 +263,12 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex justify-between'}>
|
||||||
<FormCheckRadio type='checkbox' label='Remember'>
|
<FormCheckRadio type='checkbox' label={t('pages.login.form.remember')}>
|
||||||
<Field type='checkbox' name='remember' />
|
<Field id='remember' type='checkbox' name='remember' />
|
||||||
</FormCheckRadio>
|
</FormCheckRadio>
|
||||||
|
|
||||||
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
|
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
|
||||||
Forgot password?
|
{t('pages.login.form.forgotPassword')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -249,16 +278,16 @@ export default function Login() {
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
className={'w-full'}
|
className={'w-full'}
|
||||||
type='submit'
|
type='submit'
|
||||||
label={isFetching ? 'Loading...' : 'Login'}
|
label={isFetching ? t('pages.login.form.loading') : t('pages.login.form.loginButton')}
|
||||||
color='info'
|
color='info'
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
/>
|
/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
<br />
|
<br />
|
||||||
<p className={'text-center'}>
|
<p className={'text-center'}>
|
||||||
Don’t have an account yet?{' '}
|
{t('pages.login.form.noAccountYet')}{' '}
|
||||||
<Link className={`${textColor}`} href={'/register'}>
|
<Link className={`${textColor}`} href={'/register'}>
|
||||||
New Account
|
{t('pages.login.form.newAccount')}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</Form>
|
</Form>
|
||||||
@ -268,9 +297,9 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
</SectionFullScreen>
|
</SectionFullScreen>
|
||||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. © All rights reserved</p>
|
<p className='py-6 text-sm'>{t('pages.login.footer.copyright', { year: 2026, title })}</p>
|
||||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||||
Privacy Policy
|
{t('pages.login.footer.privacy')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/payments/paymentsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/payments/paymentsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const PaymentsTablesPage = () => {
|
const PaymentsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.payments', 'Payments');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -92,28 +105,28 @@ const PaymentsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Payments')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Payments" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/payments/payments-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/payments/payments-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPaymentsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getPaymentsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const PaymentsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/permissions/permissionsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/permissions/permissionsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const PermissionsTablesPage = () => {
|
const PermissionsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.permissions', 'Permissions');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -86,28 +99,28 @@ const PermissionsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Permissions')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Permissions" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/permissions/permissions-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getPermissionsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getPermissionsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -129,9 +142,9 @@ const PermissionsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/products/productsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/products/productsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ProductsTablesPage = () => {
|
const ProductsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.products', 'Products');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const ProductsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Products')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Products" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/products/products-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/products/products-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getProductsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getProductsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const ProductsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -18,8 +18,14 @@ import { useAppDispatch } from '../stores/hooks';
|
|||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function Register() {
|
export default function Register() {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||||
@ -48,6 +54,9 @@ export default function Register() {
|
|||||||
label: org.name
|
label: org.name
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = async (value) => {
|
const handleSubmit = async (value) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -58,18 +67,18 @@ export default function Register() {
|
|||||||
const { data: response } = await axios.post('/auth/signup',formData);
|
const { data: response } = await axios.post('/auth/signup',formData);
|
||||||
await router.push('/login')
|
await router.push('/login')
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
notify('success', 'Please check your email for verification link')
|
notify('success', translate('pages.auth.checkEmailVerification', 'Please check your email for verification link'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
console.log('error: ', error)
|
console.log('error: ', error)
|
||||||
notify('error', 'Something was wrong. Try again')
|
notify('error', translate('pages.auth.genericError', 'Something was wrong. Try again'))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Login')}</title>
|
<title>{getPageTitle(translate('pages.register.pageTitle', 'Register'))}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
@ -84,7 +93,7 @@ export default function Register() {
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
|
|
||||||
<label className="block font-bold mb-2" >Organization</label>
|
<label className="block font-bold mb-2" >{translate('pages.register.organization', 'Organization')}</label>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
classNames={{
|
classNames={{
|
||||||
@ -93,16 +102,16 @@ export default function Register() {
|
|||||||
value={selectedOrganization}
|
value={selectedOrganization}
|
||||||
onChange={setSelectedOrganization}
|
onChange={setSelectedOrganization}
|
||||||
options={options}
|
options={options}
|
||||||
placeholder="Select organization..."
|
placeholder={translate('pages.register.selectOrganization', 'Select organization...')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField label='Email' help='Please enter your email'>
|
<FormField label={translate('pages.auth.emailLabel', 'Email')} help={translate('pages.auth.emailHelp', 'Please enter your email')}>
|
||||||
<Field type='email' name='email' />
|
<Field type='email' name='email' />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label='Password' help='Please enter your password'>
|
<FormField label={translate('pages.auth.passwordLabel', 'Password')} help={translate('pages.auth.passwordHelp', 'Please enter your password')}>
|
||||||
<Field type='password' name='password' />
|
<Field type='password' name='password' />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label='Confirm Password' help='Please confirm your password'>
|
<FormField label={translate('pages.auth.confirmPasswordLabel', 'Confirm Password')} help={translate('pages.auth.confirmPasswordHelp', 'Please confirm your password')}>
|
||||||
<Field type='password' name='confirm' />
|
<Field type='password' name='confirm' />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
@ -111,12 +120,12 @@ export default function Register() {
|
|||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
type='submit'
|
type='submit'
|
||||||
label={loading ? 'Loading...' : 'Register' }
|
label={loading ? translate('pages.auth.loading', 'Loading...') : translate('pages.register.submit', 'Register')}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
href={'/login'}
|
href={'/login'}
|
||||||
label={'Login'}
|
label={translate('pages.auth.login', 'Login')}
|
||||||
color='info'
|
color='info'
|
||||||
/>
|
/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/roles/rolesSlice';
|
import {setRefetch, uploadCsv} from '../../stores/roles/rolesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const RolesTablesPage = () => {
|
const RolesTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.roles', 'Roles');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -86,28 +99,28 @@ const RolesTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Roles')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Roles" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/roles/roles-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/roles/roles-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getRolesCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getRolesCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -129,9 +142,9 @@ const RolesTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
386
frontend/src/pages/schools/onboarding.tsx
Normal file
386
frontend/src/pages/schools/onboarding.tsx
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
import { mdiDomain, mdiSchoolOutline } from '@mdi/js'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import React, { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import BaseButton from '../../components/BaseButton'
|
||||||
|
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 { hasPermission } from '../../helpers/userPermissions'
|
||||||
|
import { useAppSelector } from '../../stores/hooks'
|
||||||
|
|
||||||
|
type SchoolStatus = 'active' | 'setup' | 'suspended'
|
||||||
|
|
||||||
|
type School = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
nif?: string | null
|
||||||
|
phone?: string | null
|
||||||
|
email?: string | null
|
||||||
|
province?: string | null
|
||||||
|
municipality?: string | null
|
||||||
|
address?: string | null
|
||||||
|
logoUrl?: string | null
|
||||||
|
status?: SchoolStatus | null
|
||||||
|
createdAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchoolForm = {
|
||||||
|
name: string
|
||||||
|
nif: string
|
||||||
|
phone: string
|
||||||
|
email: string
|
||||||
|
province: string
|
||||||
|
municipality: string
|
||||||
|
address: string
|
||||||
|
logoUrl: string
|
||||||
|
status: SchoolStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
const emptyForm: SchoolForm = {
|
||||||
|
name: '',
|
||||||
|
nif: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
province: '',
|
||||||
|
municipality: '',
|
||||||
|
address: '',
|
||||||
|
logoUrl: '',
|
||||||
|
status: 'setup',
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusCopy: Record<SchoolStatus, string> = {
|
||||||
|
active: 'Ativa',
|
||||||
|
setup: 'Em implantação',
|
||||||
|
suspended: 'Suspensa',
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusClasses: Record<SchoolStatus, string> = {
|
||||||
|
active: 'bg-emerald-50 text-emerald-700 ring-emerald-200',
|
||||||
|
setup: 'bg-amber-50 text-amber-700 ring-amber-200',
|
||||||
|
suspended: 'bg-rose-50 text-rose-700 ring-rose-200',
|
||||||
|
}
|
||||||
|
|
||||||
|
function readableError(error: any) {
|
||||||
|
return error?.response?.data?.message || error?.message || 'Não foi possível concluir a operação.'
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanPayload(form: SchoolForm) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(form).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputClass =
|
||||||
|
'mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 shadow-sm outline-none transition focus:border-emerald-500 focus:ring-4 focus:ring-emerald-100 dark:border-slate-700 dark:bg-slate-900 dark:text-white'
|
||||||
|
|
||||||
|
const labelClass = 'text-sm font-semibold text-slate-700 dark:text-slate-200'
|
||||||
|
|
||||||
|
const SchoolsOnboardingPage = () => {
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth)
|
||||||
|
const canCreate = currentUser && hasPermission(currentUser, 'CREATE_SCHOOLS')
|
||||||
|
|
||||||
|
const [schools, setSchools] = useState<School[]>([])
|
||||||
|
const [selectedId, setSelectedId] = useState<string>('')
|
||||||
|
const [form, setForm] = useState<SchoolForm>(emptyForm)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
const selectedSchool = useMemo(
|
||||||
|
() => schools.find((school) => school.id === selectedId) || schools[0],
|
||||||
|
[schools, selectedId],
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalActive = schools.filter((school) => (school.status || 'active') === 'active').length
|
||||||
|
const totalSetup = schools.filter((school) => (school.status || 'active') === 'setup').length
|
||||||
|
|
||||||
|
const loadSchools = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/schools?limit=50&page=0')
|
||||||
|
const rows = Array.isArray(response.data?.rows) ? response.data.rows : []
|
||||||
|
setSchools(rows)
|
||||||
|
if (!selectedId && rows[0]?.id) setSelectedId(rows[0].id)
|
||||||
|
} catch (loadError) {
|
||||||
|
console.error('Failed to load schools:', loadError)
|
||||||
|
setError(readableError(loadError))
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSchools()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleFieldChange = (field: keyof SchoolForm) => (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
||||||
|
) => {
|
||||||
|
setForm((current) => ({ ...current, [field]: event.target.value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setMessage('')
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (!form.name.trim()) {
|
||||||
|
setError('Informe o nome oficial da escola para criar o tenant.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.email.trim() && !form.email.includes('@')) {
|
||||||
|
setError('Informe um email institucional válido.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post('/schools', { data: cleanPayload(form) })
|
||||||
|
setMessage('Escola criada com sucesso. O tenant já está pronto para receber alunos, professores e módulos.')
|
||||||
|
setForm(emptyForm)
|
||||||
|
await loadSchools()
|
||||||
|
} catch (saveError) {
|
||||||
|
console.error('Failed to create school:', saveError)
|
||||||
|
setError(readableError(saveError))
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Onboarding Multi-Escola')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton icon={mdiSchoolOutline} title="Onboarding Multi-Escola" main>
|
||||||
|
<BaseButton href="/schools/schools-list" color="whiteDark" label="CRUD completo" />
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
|
<div className="mb-6 overflow-hidden rounded-3xl bg-slate-950 text-white shadow-2xl shadow-slate-300/40 dark:shadow-none">
|
||||||
|
<div className="grid gap-8 bg-[radial-gradient(circle_at_top_left,_rgba(16,185,129,0.36),_transparent_34%),linear-gradient(135deg,#06281f_0%,#0f172a_54%,#172554_100%)] p-8 lg:grid-cols-[1.35fr_0.65fr]">
|
||||||
|
<div>
|
||||||
|
<span className="inline-flex rounded-full border border-emerald-300/30 bg-white/10 px-4 py-2 text-xs font-bold uppercase tracking-[0.24em] text-emerald-100">
|
||||||
|
GB-GESTÃO ESCOLAR S.A
|
||||||
|
</span>
|
||||||
|
<h2 className="mt-5 max-w-3xl text-3xl font-black tracking-tight md:text-5xl">
|
||||||
|
Crie escolas isoladas por tenant e prepare cada instituição para operar no ERP escolar.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 max-w-2xl text-base leading-7 text-slate-200">
|
||||||
|
Este primeiro fluxo cadastra a escola-mãe do tenant com NIF, contacto, localização e estado de implantação.
|
||||||
|
A partir daqui, os restantes módulos usam o vínculo por escola para manter os dados separados.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-3 lg:grid-cols-1">
|
||||||
|
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 backdrop-blur">
|
||||||
|
<p className="text-sm text-slate-300">Escolas</p>
|
||||||
|
<p className="mt-2 text-3xl font-black">{schools.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 backdrop-blur">
|
||||||
|
<p className="text-sm text-slate-300">Ativas</p>
|
||||||
|
<p className="mt-2 text-3xl font-black text-emerald-200">{totalActive}</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-3xl border border-white/10 bg-white/10 p-5 backdrop-blur">
|
||||||
|
<p className="text-sm text-slate-300">Implantação</p>
|
||||||
|
<p className="mt-2 text-3xl font-black text-amber-200">{totalSetup}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(message || error) && (
|
||||||
|
<div
|
||||||
|
className={`mb-6 rounded-2xl px-5 py-4 text-sm font-semibold ${
|
||||||
|
error
|
||||||
|
? 'border border-rose-200 bg-rose-50 text-rose-700'
|
||||||
|
: 'border border-emerald-200 bg-emerald-50 text-emerald-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{error || message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-6 flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-emerald-600">Novo tenant</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">Dados da escola</h3>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-2xl bg-emerald-50 px-4 py-2 text-xs font-bold text-emerald-700 ring-1 ring-emerald-100">
|
||||||
|
school_id ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<label className={labelClass}>
|
||||||
|
Nome oficial *
|
||||||
|
<input className={inputClass} value={form.name} onChange={handleFieldChange('name')} placeholder="Colégio Esperança" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
NIF
|
||||||
|
<input className={inputClass} value={form.nif} onChange={handleFieldChange('nif')} placeholder="5000000000" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Telefone
|
||||||
|
<input className={inputClass} value={form.phone} onChange={handleFieldChange('phone')} placeholder="+244 923 000 000" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Email institucional
|
||||||
|
<input className={inputClass} value={form.email} onChange={handleFieldChange('email')} placeholder="secretaria@escola.co.ao" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Província
|
||||||
|
<input className={inputClass} value={form.province} onChange={handleFieldChange('province')} placeholder="Luanda" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Município
|
||||||
|
<input className={inputClass} value={form.municipality} onChange={handleFieldChange('municipality')} placeholder="Talatona" />
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Estado
|
||||||
|
<select className={inputClass} value={form.status} onChange={handleFieldChange('status')}>
|
||||||
|
<option value="setup">Em implantação</option>
|
||||||
|
<option value="active">Ativa</option>
|
||||||
|
<option value="suspended">Suspensa</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className={labelClass}>
|
||||||
|
URL do logotipo
|
||||||
|
<input className={inputClass} value={form.logoUrl} onChange={handleFieldChange('logoUrl')} placeholder="https://..." />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label className={labelClass}>
|
||||||
|
Endereço
|
||||||
|
<textarea className={`${inputClass} min-h-24`} value={form.address} onChange={handleFieldChange('address')} placeholder="Rua, bairro, referência" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row">
|
||||||
|
<BaseButton type="submit" color="info" label={isSaving ? 'A criar...' : 'Criar escola tenant'} disabled={!canCreate || isSaving} className="w-full sm:w-auto" />
|
||||||
|
<BaseButton type="button" color="whiteDark" outline label="Limpar" onClick={() => setForm(emptyForm)} className="w-full sm:w-auto" />
|
||||||
|
</div>
|
||||||
|
{!canCreate && (
|
||||||
|
<p className="rounded-2xl bg-amber-50 px-4 py-3 text-sm font-semibold text-amber-700">
|
||||||
|
A sua conta pode consultar escolas, mas não tem permissão CREATE_SCHOOLS para criar novos tenants.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<div className="grid gap-6 lg:grid-cols-[0.9fr_1.1fr] xl:grid-cols-1 2xl:grid-cols-[0.9fr_1.1fr]">
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-5 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-blue-600">Lista</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">Escolas</h3>
|
||||||
|
</div>
|
||||||
|
<BaseButton color="whiteDark" small label="Atualizar" onClick={loadSchools} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading && <p className="rounded-2xl bg-slate-50 p-5 text-sm text-slate-500">A carregar escolas...</p>}
|
||||||
|
|
||||||
|
{!isLoading && schools.length === 0 && (
|
||||||
|
<div className="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-8 text-center dark:border-slate-700 dark:bg-slate-900">
|
||||||
|
<p className="text-lg font-black text-slate-800 dark:text-white">Ainda não há escolas.</p>
|
||||||
|
<p className="mt-2 text-sm text-slate-500">Crie o primeiro tenant para iniciar a operação multi-escola.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{schools.map((school) => {
|
||||||
|
const status = (school.status || 'active') as SchoolStatus
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={school.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSelectedId(school.id)}
|
||||||
|
className={`w-full rounded-3xl border p-4 text-left transition hover:-translate-y-0.5 hover:shadow-lg ${
|
||||||
|
selectedSchool?.id === school.id
|
||||||
|
? 'border-emerald-300 bg-emerald-50/80 shadow-lg shadow-emerald-100/60 dark:bg-emerald-950/30'
|
||||||
|
: 'border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="font-black text-slate-900 dark:text-white">{school.name}</p>
|
||||||
|
<p className="mt-1 text-sm text-slate-500">{school.province || 'Província por definir'} · {school.municipality || 'Município por definir'}</p>
|
||||||
|
</div>
|
||||||
|
<span className={`shrink-0 rounded-full px-3 py-1 text-xs font-bold ring-1 ${statusClasses[status]}`}>
|
||||||
|
{statusCopy[status]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className="border-0 shadow-xl shadow-slate-200/70 dark:shadow-none">
|
||||||
|
<div className="mb-6 flex items-center gap-4">
|
||||||
|
<div className="flex h-14 w-14 items-center justify-center rounded-3xl bg-gradient-to-br from-emerald-500 to-blue-700 text-lg font-black text-white">
|
||||||
|
{selectedSchool?.name?.slice(0, 2).toUpperCase() || 'GB'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-[0.2em] text-emerald-600">Detalhe</p>
|
||||||
|
<h3 className="text-2xl font-black text-slate-900 dark:text-white">{selectedSchool?.name || 'Nenhuma escola selecionada'}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedSchool ? (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="rounded-3xl bg-slate-50 p-5 dark:bg-slate-900">
|
||||||
|
<p className="text-sm font-bold text-slate-500">Identificador do tenant</p>
|
||||||
|
<p className="mt-2 break-all font-mono text-sm text-slate-800 dark:text-slate-200">{selectedSchool.id}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
{[
|
||||||
|
['NIF', selectedSchool.nif],
|
||||||
|
['Telefone', selectedSchool.phone],
|
||||||
|
['Email', selectedSchool.email],
|
||||||
|
['Província', selectedSchool.province],
|
||||||
|
['Município', selectedSchool.municipality],
|
||||||
|
['Endereço', selectedSchool.address],
|
||||||
|
].map(([label, value]) => (
|
||||||
|
<div key={label} className="rounded-2xl border border-slate-100 p-4 dark:border-slate-700">
|
||||||
|
<p className="text-xs font-bold uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
||||||
|
<p className="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-100">{value || '—'}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-3xl border border-emerald-100 bg-emerald-50 p-5 text-emerald-800">
|
||||||
|
<p className="font-black">Próximo passo operacional</p>
|
||||||
|
<p className="mt-2 text-sm leading-6">
|
||||||
|
Use este tenant para criar utilizadores da escola, associar alunos/professores e iniciar matrículas com dados isolados.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-slate-500">
|
||||||
|
Selecione uma escola para ver os dados do tenant.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardBox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SchoolsOnboardingPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <LayoutAuthenticated permission="READ_SCHOOLS">{page}</LayoutAuthenticated>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SchoolsOnboardingPage
|
||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/schools/schoolsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/schools/schoolsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SchoolsTablesPage = () => {
|
const SchoolsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.schools', 'Schools');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -86,28 +99,28 @@ const SchoolsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Schools')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Schools" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/schools/schools-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/schools/schools-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getSchoolsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getSchoolsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -129,9 +142,9 @@ const SchoolsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react';
|
import React, { ReactElement, useEffect, useState } from 'react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import { useAppDispatch } from '../stores/hooks';
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
|
||||||
import { useAppSelector } from '../stores/hooks';
|
|
||||||
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/student_guardians/student_guardiansSlice';
|
import {setRefetch, uploadCsv} from '../../stores/student_guardians/student_guardiansSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Student_guardiansTablesPage = () => {
|
const Student_guardiansTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.studentGuardians', 'Student_guardians');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -94,28 +107,28 @@ const Student_guardiansTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Student_guardians')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Student_guardians" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/student_guardians/student_guardians-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/student_guardians/student_guardians-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getStudent_guardiansCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getStudent_guardiansCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -137,9 +150,9 @@ const Student_guardiansTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/students/studentsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/students/studentsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const StudentsTablesPage = () => {
|
const StudentsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.students', 'Students');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const StudentsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Students')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Students" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/students/students-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/students/students-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getStudentsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getStudentsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const StudentsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/subjects/subjectsSlice';
|
import {setRefetch, uploadCsv} from '../../stores/subjects/subjectsSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SubjectsTablesPage = () => {
|
const SubjectsTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.subjects', 'Subjects');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const SubjectsTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Subjects')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Subjects" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/subjects/subjects-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/subjects/subjects-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getSubjectsCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getSubjectsCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const SubjectsTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/teachers/teachersSlice';
|
import {setRefetch, uploadCsv} from '../../stores/teachers/teachersSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const TeachersTablesPage = () => {
|
const TeachersTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.teachers', 'Teachers');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -88,28 +101,28 @@ const TeachersTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Teachers')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Teachers" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/teachers/teachers-new'} color='info' label='New Item'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/teachers/teachers-new'} color='info' label={translate('common.actions.newItem', 'New Item')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getTeachersCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getTeachersCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,9 +144,9 @@ const TeachersTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
|||||||
import {setRefetch, uploadCsv} from '../../stores/users/usersSlice';
|
import {setRefetch, uploadCsv} from '../../stores/users/usersSlice';
|
||||||
|
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const UsersTablesPage = () => {
|
const UsersTablesPage = () => {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
|
const entityTitle = translate('entities.users', 'Users');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
@ -92,28 +105,28 @@ const UsersTablesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Users')}</title>
|
<title>{getPageTitle(entityTitle)}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Users" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox id="usersList" className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox id="usersList" className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/users/users-new'} color='info' label='Add/Invite User'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/users/users-new'} color='info' label={translate('common.actions.addInviteUser', 'Add/Invite User')}/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
color='info'
|
color='info'
|
||||||
label='Filter'
|
label={translate('common.actions.filter', 'Filter')}
|
||||||
onClick={addFilter}
|
onClick={addFilter}
|
||||||
/>
|
/>
|
||||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getUsersCSV} />
|
<BaseButton className={'mr-3'} color='info' label={translate('common.actions.downloadCsv', 'Download CSV')} onClick={getUsersCSV} />
|
||||||
|
|
||||||
{hasCreatePermission && (
|
{hasCreatePermission && (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color='info'
|
color='info'
|
||||||
label='Upload CSV'
|
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
onClick={() => setIsModalActive(true)}
|
onClick={() => setIsModalActive(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +148,9 @@ const UsersTablesPage = () => {
|
|||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
<CardBoxModal
|
<CardBoxModal
|
||||||
title='Upload CSV'
|
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
|
|||||||
@ -8,13 +8,23 @@ import LayoutGuest from '../layouts/Guest';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function Verify() {
|
export default function Verify() {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const [isTranslationMounted, setIsTranslationMounted] = React.useState(false);
|
||||||
|
const translate = (key: string, fallback: string): string => (
|
||||||
|
isTranslationMounted ? String(t(key, { defaultValue: fallback })) : fallback
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { token } = router.query;
|
const { token } = router.query;
|
||||||
const notify = (type, msg) => toast(msg, { type });
|
const notify = (type, msg) => toast(msg, { type });
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTranslationMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
@ -28,7 +38,7 @@ export default function Verify() {
|
|||||||
}).then(verified => {
|
}).then(verified => {
|
||||||
if (verified) {
|
if (verified) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
notify('success', 'Your email was verified');
|
notify('success', translate('pages.verifyEmail.success', 'Your email was verified'));
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -44,11 +54,11 @@ export default function Verify() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Verify Email')}</title>
|
<title>{getPageTitle(translate('pages.verifyEmail.pageTitle', 'Verify Email'))}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionFullScreen bg='violet'>
|
<SectionFullScreen bg='violet'>
|
||||||
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
<CardBox className='w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12'>
|
||||||
<p>{loading ? 'Loading...' : ''}</p>
|
<p>{loading ? translate('pages.auth.loading', 'Loading...') : ''}</p>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
</SectionFullScreen>
|
</SectionFullScreen>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user