Autosave: 20260623-140927
This commit is contained in:
parent
27e732ea5f
commit
8156349b96
@ -1,7 +1,5 @@
|
||||
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
|
||||
@ -25,6 +23,14 @@ module.exports = class SchoolsDBApi {
|
||||
||
|
||||
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,
|
||||
createdById: currentUser.id,
|
||||
@ -55,6 +61,14 @@ module.exports = class SchoolsDBApi {
|
||||
||
|
||||
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,
|
||||
createdById: currentUser.id,
|
||||
@ -74,8 +88,6 @@ module.exports = class SchoolsDBApi {
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || {id: null};
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const globalAccess = currentUser.app_role?.globalAccess;
|
||||
|
||||
const schools = await db.schools.findByPk(id, {}, {transaction});
|
||||
|
||||
|
||||
@ -84,6 +96,14 @@ module.exports = class SchoolsDBApi {
|
||||
const updatePayload = {};
|
||||
|
||||
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;
|
||||
@ -286,10 +306,6 @@ module.exports = class SchoolsDBApi {
|
||||
|
||||
offset = currentPage * limit;
|
||||
|
||||
const orderBy = null;
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
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) {
|
||||
const schools = sequelize.define(
|
||||
'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: {
|
||||
|
||||
@ -31,9 +31,22 @@ router.use(checkCrudPermissions('schools'));
|
||||
* name:
|
||||
* type: string
|
||||
* 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 }
|
||||
);
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id','name',
|
||||
|
||||
|
||||
|
||||
];
|
||||
const fields = ['id','name','nif','phone','email','province','municipality','address','logoUrl','status'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
|
||||
@ -3,20 +3,50 @@ const SchoolsDBApi = require('../db/api/schools');
|
||||
const processFile = require("../middlewares/upload");
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
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 {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await SchoolsDBApi.create(
|
||||
data,
|
||||
normalizeSchoolData(data),
|
||||
{
|
||||
currentUser,
|
||||
transaction,
|
||||
@ -28,9 +58,9 @@ module.exports = class SchoolsService {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static async bulkImport(req, res, sendInvitationEmails = true, host) {
|
||||
static async bulkImport(req, res) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
@ -81,7 +111,7 @@ module.exports = class SchoolsService {
|
||||
|
||||
const updatedSchools = await SchoolsDBApi.update(
|
||||
id,
|
||||
data,
|
||||
normalizeSchoolData(data),
|
||||
{
|
||||
currentUser,
|
||||
transaction,
|
||||
@ -95,7 +125,7 @@ module.exports = class SchoolsService {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
@ -4,34 +4,66 @@
|
||||
"pageTitle": "Dashboard",
|
||||
"overview": "Overview",
|
||||
"loadingWidgets": "Loading widgets...",
|
||||
"loading": "Loading..."
|
||||
"loading": "Loading...",
|
||||
"roleWidgets": "{{role}}'s widgets"
|
||||
},
|
||||
"login": {
|
||||
"pageTitle": "Login",
|
||||
|
||||
"form": {
|
||||
"loginLabel": "Login",
|
||||
"loginHelp": "Please enter your login",
|
||||
"passwordLabel": "Password",
|
||||
"passwordHelp": "Please enter your password",
|
||||
"remember": "Remember",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"loginButton": "Login",
|
||||
"loading": "Loading...",
|
||||
"noAccountYet": "Don’t have an account yet?",
|
||||
"newAccount": "New Account"
|
||||
"loginLabel": "Login",
|
||||
"loginHelp": "Please enter your login",
|
||||
"passwordLabel": "Password",
|
||||
"passwordHelp": "Please enter your password",
|
||||
"remember": "Remember",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"loginButton": "Login",
|
||||
"loading": "Loading...",
|
||||
"noAccountYet": "Don’t have an account yet?",
|
||||
"newAccount": "New Account"
|
||||
},
|
||||
|
||||
"pexels": {
|
||||
"photoCredit": "Photo by {{photographer}} on Pexels",
|
||||
"videoCredit": "Video by {{name}} on Pexels",
|
||||
"videoUnsupported": "Your browser does not support the video tag."
|
||||
},
|
||||
|
||||
"footer": {
|
||||
"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": {
|
||||
@ -41,12 +73,114 @@
|
||||
"settingsTitle": "Widget Creator Settings",
|
||||
"settingsDescription": "What role are we showing and creating widgets for?",
|
||||
"doneButton": "Done",
|
||||
"loading": "Loading..."
|
||||
"loading": "Loading...",
|
||||
"creationError": "Error with widget creation"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search",
|
||||
"required": "Required",
|
||||
"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 { useAppSelector } from '../stores/hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
item: MenuAsideItem
|
||||
@ -16,6 +17,8 @@ type Props = {
|
||||
const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
||||
const [isLinkActive, setIsLinkActive] = useState(false)
|
||||
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const asideMenuItemStyle = useAppSelector((state) => state.style.asideMenuItemStyle)
|
||||
const asideMenuDropdownStyle = useAppSelector((state) => state.style.asideMenuDropdownStyle)
|
||||
@ -28,6 +31,10 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
||||
|
||||
const { asPath, isReady } = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (item.href && isReady) {
|
||||
const linkPathName = new URL(item.href, location.href).pathname + '/';
|
||||
@ -40,6 +47,10 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
||||
}
|
||||
}, [item.href, isReady, asPath])
|
||||
|
||||
const translatedLabel = isMounted && item.labelKey
|
||||
? t(item.labelKey, { defaultValue: item.label })
|
||||
: item.label
|
||||
|
||||
const asideMenuItemInnerContents = (
|
||||
<>
|
||||
{item.icon && (
|
||||
@ -50,7 +61,7 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => {
|
||||
item.menu ? '' : 'pr-12'
|
||||
} ${activeClassAddon}`}
|
||||
>
|
||||
{item.label}
|
||||
{translatedLabel}
|
||||
</span>
|
||||
{item.menu && (
|
||||
<BaseIcon
|
||||
|
||||
@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js'
|
||||
import BaseIcon from './BaseIcon'
|
||||
import AsideMenuList from './AsideMenuList'
|
||||
import { MenuAsideItem } from '../interfaces'
|
||||
import { useAppSelector } from '../stores/hooks'
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||
import Link from 'next/link';
|
||||
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleAttendance = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -25,6 +26,16 @@ import { SlotInfo } from 'react-big-calendar';
|
||||
const perPage = 100
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -286,7 +297,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -309,9 +320,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -320,7 +329,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -335,22 +344,22 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -364,13 +373,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -378,11 +385,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -392,11 +399,11 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -404,12 +411,12 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -422,13 +429,13 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -438,14 +445,14 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -476,7 +483,7 @@ const TableSampleBook_loans = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleBooks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import BaseIcon from './BaseIcon';
|
||||
import { mdiFileUploadOutline } from '@mdi/js';
|
||||
|
||||
@ -9,10 +10,19 @@ type 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 [errorMessage, setErrorMessage] = useState('');
|
||||
const fileInput = React.createRef<HTMLInputElement>();
|
||||
|
||||
useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!file && fileInput) fileInput.current.value = '';
|
||||
}, [file, fileInput]);
|
||||
@ -26,7 +36,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => {
|
||||
setFile(newFile);
|
||||
setErrorMessage('');
|
||||
} 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'>
|
||||
<span className='font-semibold'>Click to upload</span> or drag
|
||||
and drop
|
||||
<span className='font-semibold'>{translate('common.upload.clickToUpload', 'Click to upload')}</span> {translate('common.upload.dragAndDrop', 'or drag and drop')}
|
||||
</p>
|
||||
{formats && (
|
||||
<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 { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleEmployees = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -24,6 +25,16 @@ import ListGrades from './ListGrades';
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -279,7 +290,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -302,9 +313,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -313,7 +322,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -328,22 +337,22 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -357,13 +366,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -371,11 +378,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -385,11 +392,11 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -397,12 +404,12 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -415,13 +422,13 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -431,14 +438,14 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -463,7 +470,7 @@ const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) =
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -25,6 +26,16 @@ import axios from 'axios';
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -312,7 +323,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -335,9 +346,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -346,7 +355,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -361,22 +370,22 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -390,13 +399,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -404,11 +411,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -418,11 +425,11 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -430,12 +437,12 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -448,13 +455,13 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -464,14 +471,14 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -498,7 +505,7 @@ const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Select, { components, SingleValueProps, OptionProps } from 'react-select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type LanguageOption = { label: string; value: string };
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
menuPlacement?: 'auto' | 'bottom' | 'top';
|
||||
width?: number;
|
||||
};
|
||||
|
||||
const LANGS: LanguageOption[] = [
|
||||
{ value: 'pt', label: '🇵🇹 PT' },
|
||||
{ value: 'en', label: '🇬🇧 EN' },
|
||||
{ value: 'fr', label: '🇫🇷 FR' },
|
||||
{ value: 'es', label: '🇪🇸 ES' },
|
||||
{ value: 'de', label: '🇩🇪 DE' },
|
||||
];
|
||||
|
||||
const Option = (props: OptionProps<LanguageOption, false>) => (
|
||||
@ -22,29 +27,33 @@ const SingleVal = (props: SingleValueProps<LanguageOption, false>) => (
|
||||
</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 [selected, setSelected] = useState<LanguageOption>(LANGS[0]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentLanguage = i18n.resolvedLanguage || i18n.language || 'pt';
|
||||
setSelected(LANGS.find((lang) => lang.value === currentLanguage) || LANGS[0]);
|
||||
setMounted(true);
|
||||
}, []);
|
||||
}, [i18n.language, i18n.resolvedLanguage]);
|
||||
|
||||
const handleChange = (opt: LanguageOption | null) => {
|
||||
if (!opt) return;
|
||||
setSelected(opt);
|
||||
i18n.changeLanguage(opt.value);
|
||||
};
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<div style={{ width: 88 }}>
|
||||
<div className={className} style={{ width }}>
|
||||
<Select
|
||||
value={selected}
|
||||
options={LANGS}
|
||||
onChange={handleChange}
|
||||
isSearchable={false}
|
||||
menuPlacement='top'
|
||||
menuPlacement={menuPlacement}
|
||||
components={{
|
||||
Option,
|
||||
SingleValue: SingleVal,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '@mui/material/Button';
|
||||
import BaseIcon from './BaseIcon';
|
||||
import {
|
||||
@ -31,6 +31,16 @@ const ListActionsPopover = ({
|
||||
pathEdit,
|
||||
pathView,
|
||||
}: 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 handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
@ -81,7 +91,7 @@ const ListActionsPopover = ({
|
||||
href={linkView}
|
||||
sx={{ justifyContent: "start" }}
|
||||
>
|
||||
View
|
||||
{translate('common.actions.view', 'View')}
|
||||
</Button>
|
||||
{hasUpdatePermission && (
|
||||
<Button
|
||||
@ -90,7 +100,7 @@ const ListActionsPopover = ({
|
||||
href={linkEdit}
|
||||
sx={{ justifyContent: "start" }}
|
||||
>
|
||||
Edit
|
||||
{translate('common.actions.edit', 'Edit')}
|
||||
</Button>
|
||||
)}
|
||||
{hasUpdatePermission && (
|
||||
@ -103,7 +113,7 @@ const ListActionsPopover = ({
|
||||
}}
|
||||
sx={{ justifyContent: "start" }}
|
||||
>
|
||||
Delete
|
||||
{translate('common.actions.delete', 'Delete')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, {useEffect, useRef} from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||
import BaseDivider from './BaseDivider'
|
||||
import BaseIcon from './BaseIcon'
|
||||
@ -12,6 +11,7 @@ import { setDarkMode } from '../stores/styleSlice'
|
||||
import { logoutUser } from '../stores/authSlice'
|
||||
import { useRouter } from 'next/router';
|
||||
import ClickOutside from "./ClickOutside";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
item: MenuNavBarItem
|
||||
@ -33,6 +33,12 @@ export default function NavBarItem({ item }: Props) {
|
||||
const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`;
|
||||
|
||||
const [isDropdownActive, setIsDropdownActive] = useState(false)
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
return () => setIsDropdownActive(false);
|
||||
@ -47,7 +53,11 @@ export default function NavBarItem({ item }: Props) {
|
||||
item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '',
|
||||
].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 = () => {
|
||||
if (item.menu) {
|
||||
@ -64,21 +74,16 @@ export default function NavBarItem({ item }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const getItemId = (label) => {
|
||||
switch (label) {
|
||||
case 'Light/Dark':
|
||||
return 'themeToggle';
|
||||
case 'Log out':
|
||||
return 'logout';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
const getItemId = () => {
|
||||
if (item.isToggleLightDark) return 'themeToggle';
|
||||
if (item.isLogout) return 'logout';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const NavBarItemComponentContents = (
|
||||
<>
|
||||
<div
|
||||
id={getItemId(itemLabel)}
|
||||
id={getItemId()}
|
||||
className={`flex items-center ${
|
||||
item.menu
|
||||
? '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 { passwordReset } from '../stores/authSlice';
|
||||
import {useAppDispatch} from '../stores/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 [isInvitation, setIsInvitation] = React.useState(false);
|
||||
const router = useRouter();
|
||||
@ -24,6 +30,10 @@ export default function PasswordSetOrReset() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (invitation) {
|
||||
setIsInvitation(true);
|
||||
@ -49,16 +59,16 @@ export default function PasswordSetOrReset() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{isInvitation && <title>{getPageTitle('Set Password')}</title>}
|
||||
{!isInvitation && <title>{getPageTitle('Reset Password')}</title>}
|
||||
{isInvitation && <title>{getPageTitle(translate('pages.password.setTitle', 'Set Password'))}</title>}
|
||||
{!isInvitation && <title>{getPageTitle(translate('pages.password.resetTitle', 'Reset Password'))}</title>}
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<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'>
|
||||
{isInvitation && <p className='text-xl mb-2'>Set Password</p>}
|
||||
{!isInvitation && <p className='text-xl mb-2'>Reset Password</p>}
|
||||
<p className='text-base mb-4'>Enter your new password</p>
|
||||
{isInvitation && <p className='text-xl mb-2'>{translate('pages.password.setTitle', 'Set Password')}</p>}
|
||||
{!isInvitation && <p className='text-xl mb-2'>{translate('pages.password.resetTitle', 'Reset Password')}</p>}
|
||||
<p className='text-base mb-4'>{translate('pages.password.enterNewPassword', 'Enter your new password')}</p>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
@ -74,7 +84,7 @@ export default function PasswordSetOrReset() {
|
||||
<Field
|
||||
type='password'
|
||||
name='password'
|
||||
placeholder='Password'
|
||||
placeholder={translate('pages.auth.passwordLabel', 'Password')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
@ -82,7 +92,7 @@ export default function PasswordSetOrReset() {
|
||||
<Field
|
||||
type='password'
|
||||
name='confirm'
|
||||
placeholder='Confirm Password'
|
||||
placeholder={translate('pages.auth.confirmPasswordLabel', 'Confirm Password')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
@ -93,10 +103,10 @@ export default function PasswordSetOrReset() {
|
||||
disabled={loading}
|
||||
label={
|
||||
loading
|
||||
? 'Loading...'
|
||||
? translate('pages.auth.loading', 'Loading...')
|
||||
: isInvitation
|
||||
? 'Set Password'
|
||||
: 'Reset Password'
|
||||
? translate('pages.password.setTitle', 'Set Password')
|
||||
: translate('pages.password.resetTitle', 'Reset Password')
|
||||
}
|
||||
color='info'
|
||||
/>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleSchools = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -43,9 +43,9 @@ export const loadColumns = async (
|
||||
|
||||
{
|
||||
field: 'name',
|
||||
headerName: 'Name',
|
||||
headerName: 'Escola',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
minWidth: 180,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
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',
|
||||
|
||||
@ -1,19 +1,26 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Search = () => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation('common');
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
const validateSearch = (value) => {
|
||||
let error;
|
||||
if (!value) {
|
||||
error = 'Required';
|
||||
error = isMounted ? t('components.search.required') : 'Required';
|
||||
} else if (value.length < 2) {
|
||||
error = 'Minimum length: 2 characters';
|
||||
error = isMounted ? t('components.search.minLength', { count: 2 }) : 'Minimum length: 2 characters';
|
||||
}
|
||||
return error;
|
||||
};
|
||||
@ -36,7 +43,7 @@ const Search = () => {
|
||||
id='search'
|
||||
name='search'
|
||||
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`}
|
||||
/>
|
||||
{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 { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleStudent_guardians = ({ filterItems, setFilterItems, filters, sh
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleStudents = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -24,6 +25,16 @@ import ListSubjects from './ListSubjects';
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -279,7 +290,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -302,9 +313,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -313,7 +322,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -328,22 +337,22 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -357,13 +366,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -371,11 +378,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -385,11 +392,11 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -397,12 +404,12 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -415,13 +422,13 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -431,14 +438,14 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -463,7 +470,7 @@ const TableSampleSubjects = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleTeachers = ({ filterItems, setFilterItems, filters, showGrid })
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
@ -22,6 +23,16 @@ import {dataGridStyles} from "../../styles";
|
||||
const perPage = 10
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
@ -277,7 +288,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
return (
|
||||
<div key={filterItem.id} className="flex mb-4">
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='selectedField'
|
||||
@ -300,9 +311,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.type === 'enum' ? (
|
||||
<div className="flex flex-col w-full mr-3">
|
||||
<div className="text-gray-500 font-bold">
|
||||
Value
|
||||
</div>
|
||||
<div className="text-gray-500 font-bold">{translate('common.filters.value', 'Value')}</div>
|
||||
<Field
|
||||
className={controlClasses}
|
||||
name="filterValue"
|
||||
@ -311,7 +320,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
>
|
||||
<option value="">Select Value</option>
|
||||
<option value="">{translate('common.filters.selectValue', 'Select Value')}</option>
|
||||
{filters.find((filter) =>
|
||||
filter.title === filterItem?.fields?.selectedField
|
||||
)?.options?.map((option) => (
|
||||
@ -326,22 +335,22 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.number ? (
|
||||
<div className="flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -355,13 +364,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
)?.date ? (
|
||||
<div className='flex flex-row 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
|
||||
className={controlClasses}
|
||||
name='filterValueFrom'
|
||||
placeholder='From'
|
||||
placeholder={translate('common.filters.from', 'From')}
|
||||
id='filterValueFrom'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueFrom || ''}
|
||||
@ -369,11 +376,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValueTo'
|
||||
placeholder='to'
|
||||
placeholder={translate('common.filters.to', 'to')}
|
||||
id='filterValueTo'
|
||||
type='datetime-local'
|
||||
value={filterItem?.fields?.filterValueTo || ''}
|
||||
@ -383,11 +390,11 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
) : (
|
||||
<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
|
||||
className={controlClasses}
|
||||
name='filterValue'
|
||||
placeholder='Contained'
|
||||
placeholder={translate('common.filters.contained', 'Contained')}
|
||||
id='filterValue'
|
||||
value={filterItem?.fields?.filterValue || ''}
|
||||
onChange={handleChange(filterItem.id)}
|
||||
@ -395,12 +402,12 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
className="my-2"
|
||||
type='reset'
|
||||
color='danger'
|
||||
label='Delete'
|
||||
label={translate('common.actions.delete', 'Delete')}
|
||||
onClick={() => {
|
||||
deleteFilter(filterItem.id)
|
||||
}}
|
||||
@ -413,13 +420,13 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className="my-2 mr-3"
|
||||
color="success"
|
||||
label='Apply'
|
||||
label={translate('common.actions.apply', 'Apply')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className="my-2"
|
||||
color='info'
|
||||
label='Cancel'
|
||||
label={translate('common.actions.cancel', 'Cancel')}
|
||||
onClick={handleReset}
|
||||
/>
|
||||
</div>
|
||||
@ -429,14 +436,14 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
</CardBox> : null
|
||||
}
|
||||
<CardBoxModal
|
||||
title="Please confirm"
|
||||
title={translate('common.confirm.title', 'Please confirm')}
|
||||
buttonColor="info"
|
||||
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
||||
buttonLabel={loading ? translate('common.actions.deleting', 'Deleting...') : translate('common.actions.confirm', 'Confirm')}
|
||||
isActive={isModalTrashActive}
|
||||
onConfirm={handleDeleteAction}
|
||||
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>
|
||||
|
||||
|
||||
@ -450,7 +457,7 @@ const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
||||
<BaseButton
|
||||
className='me-4'
|
||||
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)}
|
||||
/>,
|
||||
document.getElementById('delete-rows-button'),
|
||||
|
||||
@ -4,6 +4,7 @@ import { Field, Form, Formik } from 'formik';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import FormField from '../FormField';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
aiPrompt,
|
||||
setErrorNotification,
|
||||
@ -24,10 +25,19 @@ export const WidgetCreator = ({
|
||||
widgetsRole,
|
||||
}) => {
|
||||
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 { notify: openAiNotify } = useAppSelector((state) => state.openAi);
|
||||
|
||||
const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (openAiNotify.showNotification) {
|
||||
notify(openAiNotify.typeNotification, openAiNotify.textNotification);
|
||||
@ -67,7 +77,7 @@ export const WidgetCreator = ({
|
||||
const errorMessage =
|
||||
responcePayload.data?.error?.message || error?.message;
|
||||
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>
|
||||
<FormField
|
||||
label='Create Chart or Widget'
|
||||
label={translate('components.widgetCreator.title', 'Create Chart or Widget')}
|
||||
help={
|
||||
isFetchingQuery ?
|
||||
'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.loading', 'Loading...') :
|
||||
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} />
|
||||
@ -110,14 +120,14 @@ export const WidgetCreator = ({
|
||||
>
|
||||
{({ submitForm }) => (
|
||||
<CardBoxModal
|
||||
title='Widget Creator Settings'
|
||||
title={translate('components.widgetCreator.settingsTitle', 'Widget Creator Settings')}
|
||||
buttonColor='info'
|
||||
buttonLabel='Done'
|
||||
buttonLabel={translate('components.widgetCreator.doneButton', 'Done')}
|
||||
isActive={isModalOpen}
|
||||
onConfirm={submitForm}
|
||||
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>
|
||||
<FormField>
|
||||
|
||||
@ -8,9 +8,13 @@ i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
fallbackLng: 'pt',
|
||||
supportedLngs: ['pt', 'en'],
|
||||
load: 'languageOnly',
|
||||
defaultNS: 'common',
|
||||
ns: ['common'],
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator'],
|
||||
order: ['localStorage'],
|
||||
lookupLocalStorage: 'app_lang_',
|
||||
caches: ['localStorage'],
|
||||
},
|
||||
|
||||
@ -6,6 +6,7 @@ export type UserPayloadObject = {
|
||||
|
||||
export type MenuAsideItem = {
|
||||
label: string
|
||||
labelKey?: string
|
||||
icon?: string
|
||||
href?: string
|
||||
target?: string
|
||||
@ -18,6 +19,7 @@ export type MenuAsideItem = {
|
||||
|
||||
export type MenuNavBarItem = {
|
||||
label?: string
|
||||
labelKey?: string
|
||||
icon?: string
|
||||
href?: string
|
||||
target?: string
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { ReactNode, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import React, { ReactNode, useEffect, useState } from 'react'
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||
import menuAside from '../menuAside'
|
||||
@ -13,6 +12,8 @@ import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||
import Search from '../components/Search';
|
||||
import { useRouter } from 'next/router'
|
||||
import {findMe, logoutUser} from "../stores/authSlice";
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../helpers/userPermissions";
|
||||
|
||||
@ -32,6 +33,7 @@ export default function LayoutAuthenticated({
|
||||
}: Props) {
|
||||
const dispatch = useAppDispatch()
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const { token, currentUser } = useAppSelector((state) => state.auth)
|
||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||
let localToken
|
||||
@ -69,6 +71,11 @@ export default function LayoutAuthenticated({
|
||||
|
||||
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
|
||||
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
|
||||
const [isTranslationMounted, setIsTranslationMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsTranslationMounted(true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChangeStart = () => {
|
||||
@ -114,6 +121,9 @@ export default function LayoutAuthenticated({
|
||||
<NavBarItemPlain useMargin>
|
||||
<Search />
|
||||
</NavBarItemPlain>
|
||||
<NavBarItemPlain display="hidden md:flex" useMargin>
|
||||
<LanguageSwitcher menuPlacement="bottom" />
|
||||
</NavBarItemPlain>
|
||||
</NavBar>
|
||||
<AsideMenu
|
||||
isAsideMobileExpanded={isAsideMobileExpanded}
|
||||
@ -122,7 +132,7 @@ export default function LayoutAuthenticated({
|
||||
onAsideLgClose={() => setIsAsideLgActive(false)}
|
||||
/>
|
||||
{children}
|
||||
<FooterBar>Hand-crafted & Made with ❤️</FooterBar>
|
||||
<FooterBar>{isTranslationMounted ? t('layout.footer.madeWith') : 'Hand-crafted & Made with ❤️'}</FooterBar>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useAppSelector } from '../stores/hooks'
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
@ -11,7 +12,12 @@ export default function LayoutGuest({ children }: Props) {
|
||||
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,11 +6,13 @@ const menuAside: MenuAsideItem[] = [
|
||||
href: '/dashboard',
|
||||
icon: icon.mdiViewDashboardOutline,
|
||||
label: 'Dashboard',
|
||||
labelKey: 'navigation.aside.dashboard',
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
label: 'Users',
|
||||
labelKey: 'navigation.aside.users',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
||||
@ -19,6 +21,7 @@ const menuAside: MenuAsideItem[] = [
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Roles',
|
||||
labelKey: 'navigation.aside.roles',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
||||
@ -27,14 +30,32 @@ const menuAside: MenuAsideItem[] = [
|
||||
{
|
||||
href: '/permissions/permissions-list',
|
||||
label: 'Permissions',
|
||||
labelKey: 'navigation.aside.permissions',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||
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',
|
||||
label: 'Schools',
|
||||
label: 'Schools CRUD',
|
||||
labelKey: 'navigation.aside.schoolsCrud',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiTable ?? icon.mdiTable,
|
||||
@ -43,6 +64,7 @@ const menuAside: MenuAsideItem[] = [
|
||||
{
|
||||
href: '/students/students-list',
|
||||
label: 'Students',
|
||||
labelKey: 'navigation.aside.students',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Guardians',
|
||||
labelKey: 'navigation.aside.guardians',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Student guardians',
|
||||
labelKey: 'navigation.aside.studentGuardians',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Teachers',
|
||||
labelKey: 'navigation.aside.teachers',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Courses',
|
||||
labelKey: 'navigation.aside.courses',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Grades',
|
||||
labelKey: 'navigation.aside.grades',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Classes',
|
||||
labelKey: 'navigation.aside.classes',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Subjects',
|
||||
labelKey: 'navigation.aside.subjects',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Enrollments',
|
||||
labelKey: 'navigation.aside.enrollments',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Assessments',
|
||||
labelKey: 'navigation.aside.assessments',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Attendance',
|
||||
labelKey: 'navigation.aside.attendance',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Invoices',
|
||||
labelKey: 'navigation.aside.invoices',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Payments',
|
||||
labelKey: 'navigation.aside.payments',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Employees',
|
||||
labelKey: 'navigation.aside.employees',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Products',
|
||||
labelKey: 'navigation.aside.products',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Books',
|
||||
labelKey: 'navigation.aside.books',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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',
|
||||
label: 'Book loans',
|
||||
labelKey: 'navigation.aside.bookLoans',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiBookClock' in icon ? icon['mdiBookClock' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
@ -179,6 +217,7 @@ const menuAside: MenuAsideItem[] = [
|
||||
{
|
||||
href: '/profile',
|
||||
label: 'Profile',
|
||||
labelKey: 'navigation.aside.profile',
|
||||
icon: icon.mdiAccountCircle,
|
||||
},
|
||||
|
||||
@ -187,6 +226,7 @@ const menuAside: MenuAsideItem[] = [
|
||||
href: '/api-docs',
|
||||
target: '_blank',
|
||||
label: 'Swagger API',
|
||||
labelKey: 'navigation.aside.swaggerApi',
|
||||
icon: icon.mdiFileCode,
|
||||
permissions: 'READ_API_DOCS'
|
||||
},
|
||||
|
||||
@ -1,15 +1,7 @@
|
||||
import {
|
||||
mdiMenu,
|
||||
mdiClockOutline,
|
||||
mdiCloud,
|
||||
mdiCrop,
|
||||
mdiAccount,
|
||||
mdiCogOutline,
|
||||
mdiEmail,
|
||||
mdiLogout,
|
||||
mdiThemeLightDark,
|
||||
mdiGithub,
|
||||
mdiVuejs,
|
||||
} from '@mdi/js'
|
||||
import { MenuNavBarItem } from './interfaces'
|
||||
|
||||
@ -20,6 +12,7 @@ const menuNavBar: MenuNavBarItem[] = [
|
||||
{
|
||||
icon: mdiAccount,
|
||||
label: 'My Profile',
|
||||
labelKey: 'navigation.nav.myProfile',
|
||||
href: '/profile',
|
||||
},
|
||||
{
|
||||
@ -28,6 +21,7 @@ const menuNavBar: MenuNavBarItem[] = [
|
||||
{
|
||||
icon: mdiLogout,
|
||||
label: 'Log Out',
|
||||
labelKey: 'navigation.nav.logOut',
|
||||
isLogout: true,
|
||||
},
|
||||
],
|
||||
@ -35,19 +29,21 @@ const menuNavBar: MenuNavBarItem[] = [
|
||||
{
|
||||
icon: mdiThemeLightDark,
|
||||
label: 'Light/Dark',
|
||||
labelKey: 'navigation.nav.lightDark',
|
||||
isDesktopNoLabel: true,
|
||||
isToggleLightDark: true,
|
||||
},
|
||||
{
|
||||
icon: mdiLogout,
|
||||
label: 'Log out',
|
||||
labelKey: 'navigation.nav.logOut',
|
||||
isDesktopNoLabel: true,
|
||||
isLogout: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const webPagesNavBar = [
|
||||
|
||||
|
||||
];
|
||||
|
||||
export default menuNavBar
|
||||
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -100,28 +113,28 @@ const AssessmentsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Assessments')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Assessments" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -143,9 +156,9 @@ const AssessmentsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/attendance/attendanceSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -92,28 +105,28 @@ const AttendanceTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Attendance')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Attendance" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const AttendanceTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/book_loans/book_loansSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -96,28 +109,28 @@ const Book_loansTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Book_loans')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Book_loans" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -143,9 +156,9 @@ const Book_loansTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/books/booksSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const BooksTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Books')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Books" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const BooksTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/classes/classesSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -92,28 +105,28 @@ const ClassesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Classes')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Classes" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const ClassesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/courses/coursesSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const CoursesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Courses')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Courses" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const CoursesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -16,13 +16,19 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const Dashboard = () => {
|
||||
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 corners = useAppSelector((state) => state.style.corners);
|
||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||
|
||||
const loadingMessage = 'Loading...';
|
||||
const loadingMessage = translate('pages.dashboard.loading', 'Loading...');
|
||||
|
||||
|
||||
const [users, setUsers] = React.useState(loadingMessage);
|
||||
@ -88,6 +94,10 @@ const Dashboard = () => {
|
||||
async function getWidgets(roleId) {
|
||||
await dispatch(fetchWidgets(roleId));
|
||||
}
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
loadData().then();
|
||||
@ -103,13 +113,13 @@ const Dashboard = () => {
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{getPageTitle('Overview')}
|
||||
{getPageTitle(translate('pages.dashboard.overview', 'Overview'))}
|
||||
</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={icon.mdiChartTimelineVariant}
|
||||
title='Overview'
|
||||
title={translate('pages.dashboard.overview', 'Overview')}
|
||||
main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
@ -123,7 +133,7 @@ const Dashboard = () => {
|
||||
{!!rolesWidgets.length &&
|
||||
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
@ -137,7 +147,7 @@ const Dashboard = () => {
|
||||
size={48}
|
||||
path={icon.mdiLoading}
|
||||
/>{' '}
|
||||
Loading widgets...
|
||||
{translate('pages.dashboard.loadingWidgets', 'Loading widgets...')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -165,7 +175,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Users
|
||||
{translate('entities.users', 'Users')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{users}
|
||||
@ -193,7 +203,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Roles
|
||||
{translate('entities.roles', 'Roles')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{roles}
|
||||
@ -221,7 +231,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Permissions
|
||||
{translate('entities.permissions', 'Permissions')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{permissions}
|
||||
@ -249,7 +259,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Schools
|
||||
{translate('entities.schools', 'Schools')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{schools}
|
||||
@ -277,7 +287,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Students
|
||||
{translate('entities.students', 'Students')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{students}
|
||||
@ -305,7 +315,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Guardians
|
||||
{translate('entities.guardians', 'Guardians')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{guardians}
|
||||
@ -333,7 +343,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Student guardians
|
||||
{translate('entities.studentGuardians', 'Student guardians')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{student_guardians}
|
||||
@ -361,7 +371,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Teachers
|
||||
{translate('entities.teachers', 'Teachers')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{teachers}
|
||||
@ -389,7 +399,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Courses
|
||||
{translate('entities.courses', 'Courses')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{courses}
|
||||
@ -417,7 +427,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Grades
|
||||
{translate('entities.grades', 'Grades')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{grades}
|
||||
@ -445,7 +455,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Classes
|
||||
{translate('entities.classes', 'Classes')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{classes}
|
||||
@ -473,7 +483,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Subjects
|
||||
{translate('entities.subjects', 'Subjects')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{subjects}
|
||||
@ -501,7 +511,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Enrollments
|
||||
{translate('entities.enrollments', 'Enrollments')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{enrollments}
|
||||
@ -529,7 +539,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Assessments
|
||||
{translate('entities.assessments', 'Assessments')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{assessments}
|
||||
@ -557,7 +567,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Attendance
|
||||
{translate('entities.attendance', 'Attendance')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{attendance}
|
||||
@ -585,7 +595,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Invoices
|
||||
{translate('entities.invoices', 'Invoices')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{invoices}
|
||||
@ -613,7 +623,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Payments
|
||||
{translate('entities.payments', 'Payments')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{payments}
|
||||
@ -641,7 +651,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Employees
|
||||
{translate('entities.employees', 'Employees')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{employees}
|
||||
@ -669,7 +679,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Products
|
||||
{translate('entities.products', 'Products')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{products}
|
||||
@ -697,7 +707,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Books
|
||||
{translate('entities.books', 'Books')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{books}
|
||||
@ -725,7 +735,7 @@ const Dashboard = () => {
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Book loans
|
||||
{translate('entities.bookLoans', 'Book loans')}
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{book_loans}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/employees/employeesSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const EmployeesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Employees')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Employees" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const EmployeesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/enrollments/enrollmentsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -96,28 +109,28 @@ const EnrollmentsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Enrollments')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Enrollments" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -139,9 +152,9 @@ const EnrollmentsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -13,32 +13,42 @@ import BaseButtons from '../components/BaseButtons';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getPageTitle } from '../config';
|
||||
import axios from "axios";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 router = useRouter();
|
||||
const notify = (type, msg) => toast( msg, {type});
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (value) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const { data: response } = await axios.post('/auth/send-password-reset-email', value);
|
||||
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 () => {
|
||||
await router.push('/login')
|
||||
}, 3000)
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
console.log('error: ', error)
|
||||
notify('error', 'Something was wrong. Try again')
|
||||
notify('error', translate('pages.auth.genericError', 'Something was wrong. Try again'))
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Login')}</title>
|
||||
<title>{getPageTitle(translate('pages.forgot.pageTitle', 'Forgot password'))}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
@ -50,7 +60,7 @@ export default function Forgot() {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<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' />
|
||||
</FormField>
|
||||
|
||||
@ -59,12 +69,12 @@ export default function Forgot() {
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
type='submit'
|
||||
label={loading ? 'Loading...' : 'Submit' }
|
||||
label={loading ? translate('pages.auth.loading', 'Loading...') : translate('pages.forgot.submit', 'Submit')}
|
||||
color='info'
|
||||
/>
|
||||
<BaseButton
|
||||
href={'/login'}
|
||||
label={'Login'}
|
||||
label={translate('pages.auth.login', 'Login')}
|
||||
color='info'
|
||||
/>
|
||||
</BaseButtons>
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/grades/gradesSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const GradesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Grades')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Grades" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const GradesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/guardians/guardiansSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const GuardiansTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Guardians')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Guardians" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const GuardiansTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
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 type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
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';
|
||||
import BaseButton from '../components/BaseButton'
|
||||
import BaseIcon from '../components/BaseIcon'
|
||||
import LayoutGuest from '../layouts/Guest'
|
||||
import { getPageTitle } from '../config'
|
||||
|
||||
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() {
|
||||
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 (
|
||||
<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>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('GB-GESTÃO ESCOLAR S.A')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your GB Gestao Escolar SA app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<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>
|
||||
<p className='text-center text-gray-500'>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
<main className="min-h-screen overflow-hidden bg-[#F8FAFC] text-slate-950">
|
||||
<nav className="mx-auto flex max-w-7xl items-center justify-between px-6 py-6">
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
<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">
|
||||
GB
|
||||
</span>
|
||||
<span>
|
||||
<span className="block text-sm font-black tracking-tight">GB-GESTÃO ESCOLAR</span>
|
||||
<span className="block text-xs font-semibold text-slate-500">SaaS multi-escola</span>
|
||||
</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-3">
|
||||
<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">
|
||||
Login
|
||||
</Link>
|
||||
<BaseButton href="/login" color="info" label="Admin interface" roundedFull />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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">
|
||||
<div className="absolute left-1/2 top-24 -z-0 h-72 w-72 rounded-full bg-emerald-300/30 blur-3xl" />
|
||||
<div className="relative z-10">
|
||||
<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>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
<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>
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<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>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</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>
|
||||
);
|
||||
<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 className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{modules.map((module, index) => (
|
||||
<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">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<span className="text-sm font-black text-emerald-600 group-hover:text-emerald-300">0{index + 1}</span>
|
||||
<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>
|
||||
|
||||
<section className="px-6 py-16">
|
||||
<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>
|
||||
<p className="text-sm font-bold uppercase tracking-[0.24em] text-emerald-200">Admin interface</p>
|
||||
<h2 className="mt-2 text-3xl font-black">Entre para criar a primeira escola tenant.</h2>
|
||||
</div>
|
||||
<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">
|
||||
Abrir painel <BaseIcon path={mdiArrowRight} size={18} />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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) {
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -92,28 +105,28 @@ const InvoicesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Invoices')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Invoices" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -137,9 +150,9 @@ const InvoicesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -21,9 +21,11 @@ import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
import Link from 'next/link';
|
||||
import {toast, ToastContainer} from "react-toastify";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation('common');
|
||||
const dispatch = useAppDispatch();
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||
@ -37,6 +39,7 @@ export default function Login() {
|
||||
const [contentType, setContentType] = useState('image');
|
||||
const [contentPosition, setContentPosition] = useState('right');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const { currentUser, isFetching, errorMessage, token, notify:notifyState } = useAppSelector(
|
||||
(state) => state.auth,
|
||||
);
|
||||
@ -46,6 +49,10 @@ export default function Login() {
|
||||
|
||||
const title = 'GB Gestao Escolar SA'
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect( () => {
|
||||
async function fetchData() {
|
||||
@ -100,20 +107,30 @@ export default function Login() {
|
||||
}));
|
||||
};
|
||||
|
||||
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>
|
||||
const imageBlock = (image) => {
|
||||
const imageUrl = image?.src?.original;
|
||||
const photoCredit = t('pages.login.pexels.photoCredit', { photographer: image?.photographer || 'Pexels' });
|
||||
|
||||
return (
|
||||
<div className="hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3"
|
||||
style={{
|
||||
backgroundImage: imageUrl ? `url(${imageUrl})` : '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">
|
||||
{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>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
@ -126,7 +143,7 @@ export default function Login() {
|
||||
muted
|
||||
>
|
||||
<source src={video.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
{t('pages.login.pexels.videoUnsupported')}
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
@ -135,18 +152,22 @@ export default function Login() {
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
{t('pages.login.pexels.videoCredit', { name: video.user.name })}
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
if (!isMounted) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={contentPosition === 'background' ? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
illustrationImage?.src?.original
|
||||
? `url(${illustrationImage.src.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
@ -154,7 +175,7 @@ export default function Login() {
|
||||
backgroundRepeat: 'no-repeat',
|
||||
} : {}}>
|
||||
<Head>
|
||||
<title>{getPageTitle('Login')}</title>
|
||||
<title>{getPageTitle(t('pages.login.pageTitle'))}</title>
|
||||
</Head>
|
||||
|
||||
<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'>
|
||||
|
||||
<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>
|
||||
|
||||
<p className='mb-2'>Use{' '}
|
||||
<p className='mb-2'>
|
||||
{t('pages.login.sampleCredentialsSuperAdmin', { email: '', password: '' })}{' '}
|
||||
<code className={`cursor-pointer ${textColor} `}
|
||||
data-password="1561e4f2"
|
||||
onClick={(e) => setLogin(e.target)}>super_admin@flatlogic.com</code>{' / '}
|
||||
<code className={`${textColor}`}>1561e4f2</code>{' / '}
|
||||
to login as Super Admin</p>
|
||||
<code className={`${textColor}`}>1561e4f2</code>
|
||||
</p>
|
||||
|
||||
<p className='mb-2'>Use{' '}
|
||||
<p className='mb-2'>
|
||||
{t('pages.login.sampleCredentialsAdmin', { email: '', password: '' })}{' '}
|
||||
<code className={`cursor-pointer ${textColor} `}
|
||||
data-password="1561e4f2"
|
||||
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
|
||||
<code className={`${textColor}`}>1561e4f2</code>{' / '}
|
||||
to login as Admin</p>
|
||||
<p>Use <code
|
||||
className={`cursor-pointer ${textColor} `}
|
||||
data-password="ba8575c6f095"
|
||||
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
||||
<code className={`${textColor}`}>ba8575c6f095</code>{' / '}
|
||||
to login as User</p>
|
||||
<code className={`${textColor}`}>1561e4f2</code>
|
||||
</p>
|
||||
<p>
|
||||
{t('pages.login.sampleCredentialsUser', { email: '', password: '' })}{' '}
|
||||
<code
|
||||
className={`cursor-pointer ${textColor} `}
|
||||
data-password="ba8575c6f095"
|
||||
onClick={(e) => setLogin(e.target)}>client@hello.com</code>{' / '}
|
||||
<code className={`${textColor}`}>ba8575c6f095</code>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
@ -210,16 +237,18 @@ export default function Login() {
|
||||
>
|
||||
<Form>
|
||||
<FormField
|
||||
label='Login'
|
||||
help='Please enter your login'>
|
||||
<Field name='email' />
|
||||
label={t('pages.login.form.loginLabel')}
|
||||
labelFor='email'
|
||||
help={t('pages.login.form.loginHelp')}>
|
||||
<Field id='email' name='email' autoComplete='username' />
|
||||
</FormField>
|
||||
|
||||
<div className='relative'>
|
||||
<FormField
|
||||
label='Password'
|
||||
help='Please enter your password'>
|
||||
<Field name='password' type={showPassword ? 'text' : 'password'} />
|
||||
label={t('pages.login.form.passwordLabel')}
|
||||
labelFor='password'
|
||||
help={t('pages.login.form.passwordHelp')}>
|
||||
<Field id='password' name='password' type={showPassword ? 'text' : 'password'} autoComplete='current-password' />
|
||||
</FormField>
|
||||
<div
|
||||
className='absolute bottom-8 right-0 pr-3 flex items-center cursor-pointer'
|
||||
@ -234,12 +263,12 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
<div className={'flex justify-between'}>
|
||||
<FormCheckRadio type='checkbox' label='Remember'>
|
||||
<Field type='checkbox' name='remember' />
|
||||
<FormCheckRadio type='checkbox' label={t('pages.login.form.remember')}>
|
||||
<Field id='remember' type='checkbox' name='remember' />
|
||||
</FormCheckRadio>
|
||||
|
||||
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
|
||||
Forgot password?
|
||||
{t('pages.login.form.forgotPassword')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@ -249,16 +278,16 @@ export default function Login() {
|
||||
<BaseButton
|
||||
className={'w-full'}
|
||||
type='submit'
|
||||
label={isFetching ? 'Loading...' : 'Login'}
|
||||
label={isFetching ? t('pages.login.form.loading') : t('pages.login.form.loginButton')}
|
||||
color='info'
|
||||
disabled={isFetching}
|
||||
/>
|
||||
</BaseButtons>
|
||||
<br />
|
||||
<p className={'text-center'}>
|
||||
Don’t have an account yet?{' '}
|
||||
{t('pages.login.form.noAccountYet')}{' '}
|
||||
<Link className={`${textColor}`} href={'/register'}>
|
||||
New Account
|
||||
{t('pages.login.form.newAccount')}
|
||||
</Link>
|
||||
</p>
|
||||
</Form>
|
||||
@ -268,9 +297,9 @@ export default function Login() {
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<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/'>
|
||||
Privacy Policy
|
||||
{t('pages.login.footer.privacy')}
|
||||
</Link>
|
||||
</div>
|
||||
<ToastContainer />
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/payments/paymentsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -92,28 +105,28 @@ const PaymentsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Payments')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Payments" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const PaymentsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/permissions/permissionsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -86,28 +99,28 @@ const PermissionsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Permissions')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Permissions" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -129,9 +142,9 @@ const PermissionsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/products/productsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const ProductsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Products')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Products" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const ProductsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -18,8 +18,14 @@ import { useAppDispatch } from '../stores/hooks';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
import axios from "axios";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 router = useRouter();
|
||||
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
||||
@ -48,6 +54,9 @@ export default function Register() {
|
||||
label: org.name
|
||||
}));
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (value) => {
|
||||
setLoading(true)
|
||||
@ -58,18 +67,18 @@ export default function Register() {
|
||||
const { data: response } = await axios.post('/auth/signup',formData);
|
||||
await router.push('/login')
|
||||
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) {
|
||||
setLoading(false)
|
||||
console.log('error: ', error)
|
||||
notify('error', 'Something was wrong. Try again')
|
||||
notify('error', translate('pages.auth.genericError', 'Something was wrong. Try again'))
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Login')}</title>
|
||||
<title>{getPageTitle(translate('pages.register.pageTitle', 'Register'))}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
@ -84,7 +93,7 @@ export default function Register() {
|
||||
>
|
||||
<Form>
|
||||
|
||||
<label className="block font-bold mb-2" >Organization</label>
|
||||
<label className="block font-bold mb-2" >{translate('pages.register.organization', 'Organization')}</label>
|
||||
|
||||
<Select
|
||||
classNames={{
|
||||
@ -93,16 +102,16 @@ export default function Register() {
|
||||
value={selectedOrganization}
|
||||
onChange={setSelectedOrganization}
|
||||
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' />
|
||||
</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' />
|
||||
</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' />
|
||||
</FormField>
|
||||
|
||||
@ -111,12 +120,12 @@ export default function Register() {
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
type='submit'
|
||||
label={loading ? 'Loading...' : 'Register' }
|
||||
label={loading ? translate('pages.auth.loading', 'Loading...') : translate('pages.register.submit', 'Register')}
|
||||
color='info'
|
||||
/>
|
||||
<BaseButton
|
||||
href={'/login'}
|
||||
label={'Login'}
|
||||
label={translate('pages.auth.login', 'Login')}
|
||||
color='info'
|
||||
/>
|
||||
</BaseButtons>
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/roles/rolesSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -86,28 +99,28 @@ const RolesTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Roles')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Roles" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -129,9 +142,9 @@ const RolesTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -86,28 +99,28 @@ const SchoolsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Schools')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Schools" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -129,9 +142,9 @@ const SchoolsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/student_guardians/student_guardiansSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -94,28 +107,28 @@ const Student_guardiansTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Student_guardians')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Student_guardians" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -137,9 +150,9 @@ const Student_guardiansTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/students/studentsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const StudentsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Students')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Students" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const StudentsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/subjects/subjectsSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const SubjectsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Subjects')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Subjects" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const SubjectsTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/teachers/teachersSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -88,28 +101,28 @@ const TeachersTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Teachers')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Teachers" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -131,9 +144,9 @@ const TeachersTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -17,11 +17,24 @@ import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/users/usersSlice';
|
||||
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
|
||||
|
||||
|
||||
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 [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -92,28 +105,28 @@ const UsersTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Users')}</title>
|
||||
<title>{getPageTitle(entityTitle)}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Users" main>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={entityTitle} main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<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
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
label={translate('common.actions.filter', 'Filter')}
|
||||
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 && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
label={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
@ -135,9 +148,9 @@ const UsersTablesPage = () => {
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
title={translate('common.actions.uploadCsv', 'Upload CSV')}
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
buttonLabel={translate('common.actions.confirm', 'Confirm')}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
|
||||
@ -8,13 +8,23 @@ import LayoutGuest from '../layouts/Guest';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getPageTitle } from '../config';
|
||||
import axios from 'axios';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 router = useRouter();
|
||||
const { token } = router.query;
|
||||
const notify = (type, msg) => toast(msg, { type });
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsTranslationMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!token) {
|
||||
router.push('/login');
|
||||
@ -28,7 +38,7 @@ export default function Verify() {
|
||||
}).then(verified => {
|
||||
if (verified) {
|
||||
setLoading(false);
|
||||
notify('success', 'Your email was verified');
|
||||
notify('success', translate('pages.verifyEmail.success', 'Your email was verified'));
|
||||
}
|
||||
}).catch(error => {
|
||||
setLoading(false);
|
||||
@ -44,11 +54,11 @@ export default function Verify() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Verify Email')}</title>
|
||||
<title>{getPageTitle(translate('pages.verifyEmail.pageTitle', 'Verify Email'))}</title>
|
||||
</Head>
|
||||
<SectionFullScreen bg='violet'>
|
||||
<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>
|
||||
</SectionFullScreen>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user