Autosave: 20260203-130645
This commit is contained in:
parent
88aaaf8f2d
commit
6ee47994e6
@ -87,7 +87,7 @@ module.exports = class UsersDBApi {
|
|||||||
matriculePaie: data.data.matriculePaie || null,
|
matriculePaie: data.data.matriculePaie || null,
|
||||||
workdayId: data.data.workdayId || null,
|
workdayId: data.data.workdayId || null,
|
||||||
productionSite: data.data.productionSite || null,
|
productionSite: data.data.productionSite || null,
|
||||||
remoteWork: data.data.remoteWork || null,
|
remoteWork: data.data.remoteWork === true ? 'Oui' : (data.data.remoteWork === false ? 'Non' : (data.data.remoteWork || null)),
|
||||||
hiringDate: data.data.hiringDate || null,
|
hiringDate: data.data.hiringDate || null,
|
||||||
positionEntryDate: data.data.positionEntryDate || null,
|
positionEntryDate: data.data.positionEntryDate || null,
|
||||||
departureDate: data.data.departureDate || null,
|
departureDate: data.data.departureDate || null,
|
||||||
@ -227,6 +227,7 @@ module.exports = class UsersDBApi {
|
|||||||
position: item.position || null,
|
position: item.position || null,
|
||||||
team: item.team || null,
|
team: item.team || null,
|
||||||
departmentId: item.department || item.departmentId || null,
|
departmentId: item.department || item.departmentId || null,
|
||||||
|
app_roleId: item.app_role || item.app_roleId || null,
|
||||||
|
|
||||||
importHash: item.importHash || null,
|
importHash: item.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
@ -235,20 +236,25 @@ module.exports = class UsersDBApi {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Bulk create items
|
// Bulk create items
|
||||||
const users = await db.users.bulkCreate(usersData, { transaction });
|
const users = await db.users.bulkCreate(usersData, {
|
||||||
|
transaction,
|
||||||
|
ignoreDuplicates: options.ignoreDuplicates,
|
||||||
|
validate: options.validate
|
||||||
|
});
|
||||||
|
|
||||||
// For each item created, replace relation files
|
// For each item created, replace relation files only if avatar is provided
|
||||||
|
|
||||||
for (let i = 0; i < users.length; i++) {
|
for (let i = 0; i < users.length; i++) {
|
||||||
await FileDBApi.replaceRelationFiles(
|
if (data[i].avatar) {
|
||||||
{
|
await FileDBApi.replaceRelationFiles(
|
||||||
belongsTo: db.users.getTableName(),
|
{
|
||||||
belongsToColumn: 'avatar',
|
belongsTo: db.users.getTableName(),
|
||||||
belongsToId: users[i].id,
|
belongsToColumn: 'avatar',
|
||||||
},
|
belongsToId: users[i].id,
|
||||||
data[i].avatar,
|
},
|
||||||
options,
|
data[i].avatar,
|
||||||
);
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -324,7 +330,9 @@ module.exports = class UsersDBApi {
|
|||||||
if (data.matriculePaie !== undefined) updatePayload.matriculePaie = data.matriculePaie;
|
if (data.matriculePaie !== undefined) updatePayload.matriculePaie = data.matriculePaie;
|
||||||
if (data.workdayId !== undefined) updatePayload.workdayId = data.workdayId;
|
if (data.workdayId !== undefined) updatePayload.workdayId = data.workdayId;
|
||||||
if (data.productionSite !== undefined) updatePayload.productionSite = data.productionSite;
|
if (data.productionSite !== undefined) updatePayload.productionSite = data.productionSite;
|
||||||
if (data.remoteWork !== undefined) updatePayload.remoteWork = data.remoteWork;
|
if (data.remoteWork !== undefined) {
|
||||||
|
updatePayload.remoteWork = data.remoteWork === true ? 'Oui' : (data.remoteWork === false ? 'Non' : data.remoteWork);
|
||||||
|
}
|
||||||
if (data.hiringDate !== undefined) updatePayload.hiringDate = data.hiringDate;
|
if (data.hiringDate !== undefined) updatePayload.hiringDate = data.hiringDate;
|
||||||
if (data.positionEntryDate !== undefined) updatePayload.positionEntryDate = data.positionEntryDate;
|
if (data.positionEntryDate !== undefined) updatePayload.positionEntryDate = data.positionEntryDate;
|
||||||
if (data.departureDate !== undefined) updatePayload.departureDate = data.departureDate;
|
if (data.departureDate !== undefined) updatePayload.departureDate = data.departureDate;
|
||||||
@ -1022,4 +1030,4 @@ module.exports = class UsersDBApi {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const util = require('util');
|
const util = require('util');
|
||||||
const Multer = require('multer');
|
const Multer = require('multer');
|
||||||
const maxSize = 10 * 1024 * 1024;
|
const maxSize = 50 * 1024 * 1024;
|
||||||
|
|
||||||
let processFile = Multer({
|
let processFile = Multer({
|
||||||
storage: Multer.memoryStorage(),
|
storage: Multer.memoryStorage(),
|
||||||
@ -8,4 +8,4 @@ let processFile = Multer({
|
|||||||
}).single("file");
|
}).single("file");
|
||||||
|
|
||||||
let processFileMiddleware = util.promisify(processFile);
|
let processFileMiddleware = util.promisify(processFile);
|
||||||
module.exports = processFileMiddleware;
|
module.exports = processFileMiddleware;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
|
||||||
const UsersService = require('../services/users');
|
const UsersService = require('../services/users');
|
||||||
@ -297,11 +296,23 @@ router.get('/', wrapAsync(async (req, res) => {
|
|||||||
req.query, { currentUser }
|
req.query, { currentUser }
|
||||||
);
|
);
|
||||||
if (filetype && filetype === 'csv') {
|
if (filetype && filetype === 'csv') {
|
||||||
const fields = ['id','firstName','lastName','phoneNumber','email',
|
const fields = [
|
||||||
|
{ label: 'Matricule Paie', value: 'matriculePaie' },
|
||||||
|
{ label: 'WD ID', value: 'workdayId' },
|
||||||
|
{ label: 'Nom', value: 'lastName' },
|
||||||
];
|
{ label: 'Prénom', value: 'firstName' },
|
||||||
|
{ label: 'N° Tel', value: 'phoneNumber' },
|
||||||
|
{ label: 'Mail professionnel', value: 'email' },
|
||||||
|
{ label: 'Site de production', value: 'productionSite' },
|
||||||
|
{ label: 'Télétravail', value: 'remoteWork' },
|
||||||
|
{ label: 'Date d\'embauche', value: 'hiringDate' },
|
||||||
|
{ label: 'Date d\'entrée poste', value: 'positionEntryDate' },
|
||||||
|
{ label: 'Date de départ', value: 'departureDate' },
|
||||||
|
{ label: 'Département', value: (row) => row.department ? row.department.name : '' },
|
||||||
|
{ label: 'Service', value: 'service' },
|
||||||
|
{ label: 'Poste', value: 'position' },
|
||||||
|
{ label: 'Équipe (N+1)', value: 'team' },
|
||||||
|
];
|
||||||
const opts = { fields };
|
const opts = { fields };
|
||||||
try {
|
try {
|
||||||
const csv = parse(payload.rows, opts);
|
const csv = parse(payload.rows, opts);
|
||||||
@ -437,4 +448,4 @@ router.get('/:id', wrapAsync(async (req, res) => {
|
|||||||
|
|
||||||
router.use('/', require('../helpers').commonErrorHandler);
|
router.use('/', require('../helpers').commonErrorHandler);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@ -2,11 +2,20 @@ const config = require('../../config');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
let transporter = null;
|
||||||
|
|
||||||
module.exports = class EmailSender {
|
module.exports = class EmailSender {
|
||||||
constructor(email) {
|
constructor(email) {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTransporter() {
|
||||||
|
if (!transporter && EmailSender.isConfigured) {
|
||||||
|
transporter = nodemailer.createTransport(config.email);
|
||||||
|
}
|
||||||
|
return transporter;
|
||||||
|
}
|
||||||
|
|
||||||
async send() {
|
async send() {
|
||||||
assert(this.email, 'email is required');
|
assert(this.email, 'email is required');
|
||||||
assert(this.email.to, 'email.to is required');
|
assert(this.email.to, 'email.to is required');
|
||||||
@ -15,7 +24,11 @@ module.exports = class EmailSender {
|
|||||||
|
|
||||||
const htmlContent = await this.email.html();
|
const htmlContent = await this.email.html();
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport(this.transportConfig);
|
const currentTransporter = EmailSender.getTransporter();
|
||||||
|
if (!currentTransporter) {
|
||||||
|
console.warn('Email sender not configured, skipping email to:', this.email.to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: this.from,
|
from: this.from,
|
||||||
@ -27,7 +40,7 @@ module.exports = class EmailSender {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return transporter.sendMail(mailOptions);
|
return currentTransporter.sendMail(mailOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get isConfigured() {
|
static get isConfigured() {
|
||||||
@ -41,4 +54,4 @@ module.exports = class EmailSender {
|
|||||||
get from() {
|
get from() {
|
||||||
return config.email.from;
|
return config.email.from;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -50,6 +50,8 @@ const errors = {
|
|||||||
importHashRequired: 'Import hash is required',
|
importHashRequired: 'Import hash is required',
|
||||||
importHashExistent: 'Data has already been imported',
|
importHashExistent: 'Data has already been imported',
|
||||||
userEmailMissing: 'Some items in the CSV do not have an email',
|
userEmailMissing: 'Some items in the CSV do not have an email',
|
||||||
|
noRowsFound: 'No rows found in the file or header mapping failed',
|
||||||
|
fileRequired: 'File is required',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -101,4 +103,4 @@ const errors = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = errors;
|
module.exports = errors;
|
||||||
@ -6,6 +6,7 @@ const csv = require('csv-parser');
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
|
||||||
const InvitationEmail = require('./email/list/invitation');
|
const InvitationEmail = require('./email/list/invitation');
|
||||||
@ -52,54 +53,214 @@ module.exports = class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async bulkImport(req, res, sendInvitationEmails = true, host) {
|
static async bulkImport(req, res, sendInvitationEmails = true, host) {
|
||||||
const transaction = await db.sequelize.transaction();
|
console.log('Starting bulk import...');
|
||||||
let emailsToInvite = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await processFile(req, res);
|
await processFile(req, res);
|
||||||
const bufferStream = new stream.PassThrough();
|
|
||||||
const results = [];
|
if (!req.file || !req.file.buffer) {
|
||||||
|
throw new ValidationError('importer.errors.fileRequired');
|
||||||
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
bufferStream
|
|
||||||
.pipe(csv())
|
|
||||||
.on('data', (data) => results.push(data))
|
|
||||||
.on('end', () => {
|
|
||||||
console.log('results csv', results);
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.on('error', (error) => reject(error));
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasAllEmails = results.every((result) => result.email);
|
|
||||||
|
|
||||||
if (!hasAllEmails) {
|
|
||||||
throw new ValidationError('importer.errors.userEmailMissing');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await UsersDBApi.bulkImport(results, {
|
console.log('File received, size:', req.file.size);
|
||||||
transaction,
|
|
||||||
ignoreDuplicates: true,
|
// Detect separator
|
||||||
validate: true,
|
const content = req.file.buffer.toString('utf-8');
|
||||||
currentUser: req.currentUser
|
const firstLine = content.split('\n')[0];
|
||||||
|
let separator = ',';
|
||||||
|
if (firstLine.includes(';')) separator = ';';
|
||||||
|
console.log(`Detected separator: "${separator}"`);
|
||||||
|
|
||||||
|
// Get all departments to map names to IDs
|
||||||
|
const departments = await db.departments.findAll();
|
||||||
|
const departmentMap = {};
|
||||||
|
departments.forEach(dept => {
|
||||||
|
if (dept.name) {
|
||||||
|
departmentMap[dept.name.toLowerCase().trim()] = dept.id;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
emailsToInvite = results.map((result) => result.email);
|
// Get existing emails to avoid duplicates
|
||||||
|
const existingUsers = await db.users.findAll({ attributes: ['email'], paranoid: false });
|
||||||
|
const existingEmails = new Set(existingUsers.map(u => u.email?.toLowerCase().trim()));
|
||||||
|
|
||||||
|
// Get the default role
|
||||||
|
const defaultRoleName = config.roles?.user || 'Employee';
|
||||||
|
const userRole = await db.roles.findOne({ where: { name: defaultRoleName } });
|
||||||
|
|
||||||
|
const headerMapping = {
|
||||||
|
'email': 'email',
|
||||||
|
'e-mail': 'email',
|
||||||
|
'mail professionnel': 'email',
|
||||||
|
'prénom': 'firstName',
|
||||||
|
'prenom': 'firstName',
|
||||||
|
'nom': 'lastName',
|
||||||
|
'téléphone': 'phoneNumber',
|
||||||
|
'telephone': 'phoneNumber',
|
||||||
|
'n° tel': 'phoneNumber',
|
||||||
|
'matricule': 'matriculePaie',
|
||||||
|
'matricule paie': 'matriculePaie',
|
||||||
|
'wd id': 'workdayId',
|
||||||
|
'workday': 'workdayId',
|
||||||
|
'site': 'productionSite',
|
||||||
|
'site de production': 'productionSite',
|
||||||
|
'télétravail': 'remoteWork',
|
||||||
|
'teletravail': 'remoteWork',
|
||||||
|
'date d\'embauche': 'hiringDate',
|
||||||
|
'embauche': 'hiringDate',
|
||||||
|
'date d\'entrée': 'positionEntryDate',
|
||||||
|
'date d\'entrée poste': 'positionEntryDate',
|
||||||
|
'entrée poste': 'positionEntryDate',
|
||||||
|
'date de départ': 'departureDate',
|
||||||
|
'départ': 'departureDate',
|
||||||
|
'service': 'service',
|
||||||
|
'poste': 'position',
|
||||||
|
'équipe': 'team',
|
||||||
|
'equipe': 'team',
|
||||||
|
'équipe (n+1)': 'team',
|
||||||
|
'département': 'department',
|
||||||
|
'departement': 'department'
|
||||||
|
};
|
||||||
|
|
||||||
|
const bufferStream = new stream.PassThrough();
|
||||||
|
let currentBatch = [];
|
||||||
|
const batchSize = 1000;
|
||||||
|
let totalProcessed = 0;
|
||||||
|
let emailsToInvite = [];
|
||||||
|
|
||||||
|
const processBatch = async (batch) => {
|
||||||
|
if (batch.length === 0) return;
|
||||||
|
|
||||||
|
const transaction = await db.sequelize.transaction();
|
||||||
|
try {
|
||||||
|
await UsersDBApi.bulkImport(batch, {
|
||||||
|
transaction,
|
||||||
|
ignoreDuplicates: true,
|
||||||
|
validate: false, // Disable validation for speed in large imports
|
||||||
|
currentUser: req.currentUser
|
||||||
|
});
|
||||||
|
await transaction.commit();
|
||||||
|
totalProcessed += batch.length;
|
||||||
|
console.log(`Processed batch of ${batch.length}. Total processed: ${totalProcessed}`);
|
||||||
|
} catch (error) {
|
||||||
|
await transaction.rollback();
|
||||||
|
console.error('Batch processing error:', error);
|
||||||
|
// Continue with next batch? For now, we stop on first error in batch
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsePromise = new Promise((resolve, reject) => {
|
||||||
|
bufferStream
|
||||||
|
.pipe(csv({
|
||||||
|
separator: separator,
|
||||||
|
mapHeaders: ({ header }) => {
|
||||||
|
const lowerHeader = header.toLowerCase().trim();
|
||||||
|
const cleanHeader = lowerHeader.replace(/^\uFEFF/, '');
|
||||||
|
return headerMapping[cleanHeader] || cleanHeader;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.on('data', async (data) => {
|
||||||
|
// Clean up data
|
||||||
|
Object.keys(data).forEach(key => {
|
||||||
|
if (typeof data[key] === 'string') {
|
||||||
|
data[key] = data[key].trim();
|
||||||
|
if (data[key] === '') data[key] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = data.email?.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (email && email.includes('@') && !existingEmails.has(email)) {
|
||||||
|
if (data.department) {
|
||||||
|
const deptName = data.department.toLowerCase().trim();
|
||||||
|
if (departmentMap[deptName]) {
|
||||||
|
data.departmentId = departmentMap[deptName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.remoteWork) {
|
||||||
|
const val = data.remoteWork.toLowerCase().trim();
|
||||||
|
if (['oui', 'yes', 'y', 'true'].includes(val)) data.remoteWork = 'Oui';
|
||||||
|
else if (['non', 'no', 'n', 'false'].includes(val)) data.remoteWork = 'Non';
|
||||||
|
}
|
||||||
|
|
||||||
|
['hiringDate', 'positionEntryDate', 'departureDate'].forEach(field => {
|
||||||
|
if (data[field]) {
|
||||||
|
const parsedDate = moment(data[field], ['DD/MM/YYYY', 'YYYY-MM-DD', 'MM/DD/YYYY'], true);
|
||||||
|
data[field] = parsedDate.isValid() ? parsedDate.toDate() : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data.app_roleId && userRole) {
|
||||||
|
data.app_roleId = userRole.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBatch.push(data);
|
||||||
|
existingEmails.add(email);
|
||||||
|
emailsToInvite.push(email);
|
||||||
|
|
||||||
|
if (currentBatch.length >= batchSize) {
|
||||||
|
const batchToProcess = [...currentBatch];
|
||||||
|
currentBatch = [];
|
||||||
|
// We need to pause the stream to wait for the batch to be processed
|
||||||
|
bufferStream.pause();
|
||||||
|
processBatch(batchToProcess)
|
||||||
|
.then(() => bufferStream.resume())
|
||||||
|
.catch(err => {
|
||||||
|
bufferStream.destroy(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('end', async () => {
|
||||||
|
try {
|
||||||
|
if (currentBatch.length > 0) {
|
||||||
|
await processBatch(currentBatch);
|
||||||
|
}
|
||||||
|
console.log('CSV parsing and batch processing finished. Total:', totalProcessed);
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error('CSV parsing error:', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
bufferStream.end(req.file.buffer);
|
||||||
|
await parsePromise;
|
||||||
|
|
||||||
|
if (totalProcessed === 0) {
|
||||||
|
throw new ValidationError('importer.errors.noRowsFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send emails in background to avoid blocking the response further
|
||||||
|
if (emailsToInvite.length > 0 && sendInvitationEmails) {
|
||||||
|
console.log(`Starting background email sending for ${emailsToInvite.length} users...`);
|
||||||
|
// Use a simple background loop with delays to avoid overwhelming SMTP
|
||||||
|
const sendEmailsInBackground = async (emails) => {
|
||||||
|
const batchSize = 50;
|
||||||
|
for (let i = 0; i < emails.length; i += batchSize) {
|
||||||
|
const batch = emails.slice(i, i + batchSize);
|
||||||
|
await Promise.all(batch.map(email =>
|
||||||
|
AuthService.sendPasswordResetEmail(email, 'invitation', host).catch(err => console.error(`Failed to send email to ${email}:`, err))
|
||||||
|
));
|
||||||
|
console.log(`Sent email batch ${i / batchSize + 1}. Total sent: ${Math.min(i + batchSize, emails.length)}`);
|
||||||
|
// Small delay between batches
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sendEmailsInBackground(emailsToInvite);
|
||||||
|
}
|
||||||
|
|
||||||
await transaction.commit();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await transaction.rollback();
|
console.error('Bulk import error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emailsToInvite && emailsToInvite.length && !sendInvitationEmails) {
|
|
||||||
|
|
||||||
emailsToInvite.forEach((email) => {
|
|
||||||
AuthService.sendPasswordResetEmail(email, 'invitation', host);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(data, id, currentUser) {
|
static async update(data, id, currentUser) {
|
||||||
@ -167,5 +328,3 @@ module.exports = class UsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
frontend/public/assets/vm-shot-2026-02-03T13-06-18-400Z.jpg
Normal file
BIN
frontend/public/assets/vm-shot-2026-02-03T13-06-18-400Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
frontend/public/assets/vm-shot-2026-02-03T13-06-37-301Z.jpg
Normal file
BIN
frontend/public/assets/vm-shot-2026-02-03T13-06-37-301Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@ -36,36 +36,6 @@ export const loadColumns = async (
|
|||||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_USERS')
|
const hasUpdatePermission = hasPermission(user, 'UPDATE_USERS')
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
field: 'firstName',
|
|
||||||
headerName: 'First Name',
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 120,
|
|
||||||
filterable: false,
|
|
||||||
headerClassName: 'datagrid--header',
|
|
||||||
cellClassName: 'datagrid--cell',
|
|
||||||
editable: hasUpdatePermission,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'lastName',
|
|
||||||
headerName: 'Last Name',
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 120,
|
|
||||||
filterable: false,
|
|
||||||
headerClassName: 'datagrid--header',
|
|
||||||
cellClassName: 'datagrid--cell',
|
|
||||||
editable: hasUpdatePermission,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'email',
|
|
||||||
headerName: 'E-Mail',
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 150,
|
|
||||||
filterable: false,
|
|
||||||
headerClassName: 'datagrid--header',
|
|
||||||
cellClassName: 'datagrid--cell',
|
|
||||||
editable: hasUpdatePermission,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'matriculePaie',
|
field: 'matriculePaie',
|
||||||
headerName: 'Matricule Paie',
|
headerName: 'Matricule Paie',
|
||||||
@ -86,6 +56,102 @@ export const loadColumns = async (
|
|||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'lastName',
|
||||||
|
headerName: 'Nom',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'firstName',
|
||||||
|
headerName: 'Prénom',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'phoneNumber',
|
||||||
|
headerName: 'N° Tel',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'email',
|
||||||
|
headerName: 'Mail professionnel',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 180,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'productionSite',
|
||||||
|
headerName: 'Site de production',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 150,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remoteWork',
|
||||||
|
headerName: 'Télétravail',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 100,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'hiringDate',
|
||||||
|
headerName: 'Date d\'embauche',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 150,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
|
dataFormatter.dateFormatter(params.value),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'positionEntryDate',
|
||||||
|
headerName: 'Date d\'entrée poste',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 150,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
|
dataFormatter.dateFormatter(params.value),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'departureDate',
|
||||||
|
headerName: 'Date de départ',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 150,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
|
dataFormatter.dateFormatter(params.value),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'department',
|
field: 'department',
|
||||||
headerName: 'Département',
|
headerName: 'Département',
|
||||||
@ -124,10 +190,10 @@ export const loadColumns = async (
|
|||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'productionSite',
|
field: 'team',
|
||||||
headerName: 'Site de production',
|
headerName: 'Équipe (N+1)',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 150,
|
minWidth: 120,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
|
|||||||
@ -149,7 +149,7 @@ const Dashboard = () => {
|
|||||||
<div className="flex justify-between align-center">
|
<div className="flex justify-between align-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
Users
|
Collaborateurs
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl leading-tight font-semibold">
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
{users}
|
{users}
|
||||||
@ -349,4 +349,4 @@ Dashboard.getLayout = function getLayout(page: ReactElement) {
|
|||||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>
|
return <LayoutAuthenticated>{page}</LayoutAuthenticated>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Dashboard
|
export default Dashboard
|
||||||
@ -5,7 +5,11 @@ events {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
client_max_body_size 10M;
|
client_max_body_size 50M;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default upgrade;
|
default upgrade;
|
||||||
'' close;
|
'' close;
|
||||||
@ -93,4 +97,4 @@ http {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user