This commit is contained in:
Flatlogic Bot 2026-01-06 15:05:00 +00:00
parent 3ff3cd3763
commit 95aef58de7
177 changed files with 2839 additions and 16349 deletions

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
/backend/node_modules
/frontend/node_modules
node_modules/
*/node_modules/
*/build/
**/node_modules/
*/build/

View File

@ -129,8 +129,8 @@
<p class="tip">The application is currently launching. The page will automatically refresh once site is
available.</p>
<div class="project-info">
<h2>Team Projects Hub</h2>
<p>Team Projects Hub for collaborative project and task management with roles, deadlines, and reporting.</p>
<h2>TeamFlow Manager</h2>
<p>TeamFlow Manager: collaborative project and task management with roles, deadlines, calendars, and reporting.</p>
</div>
<div class="loader-container">
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"

View File

@ -1,6 +1,6 @@
# Team Projects Hub
# TeamFlow Manager
## This project was generated by [Flatlogic Platform](https://flatlogic.com).

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
const config = {
admin_pass: "d02fa926",
admin_pass: "156fca4c",
admin_email: "admin@flatlogic.com",
schema_encryption_key: process.env.SCHEMA_ENCRYPTION_KEY || '',
project_uuid: 'd02fa926-6491-481e-a115-b27b3ff8dc07',
project_uuid: '156fca4c-57d0-4fe3-8a0f-94f5514e3457',
flHost: process.env.NODE_ENV === 'production' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
gitea_domain: process.env.GITEA_DOMAIN || 'gitea.flatlogic.app',

View File

@ -1,6 +1,6 @@
DB_NAME=app_37270
DB_USER=app_37270
DB_PASS=d02fa926-6491-481e-a115-b27b3ff8dc07
DB_NAME=app_37292
DB_USER=app_37292
DB_PASS=156fca4c-57d0-4fe3-8a0f-94f5514e3457
DB_HOST=127.0.0.1
DB_PORT=5432
PORT=3000

View File

@ -1,5 +1,5 @@
#Team Projects Hub - template backend,
#TeamFlow Manager - template backend,
#### Run App on local machine:
@ -30,10 +30,10 @@
- `psql postgres -U admin`
- Type this command to creating a new database.
- `postgres=> CREATE DATABASE db_team_projects_hub;`
- `postgres=> CREATE DATABASE db_teamflow_manager;`
- Then give that new user privileges to the new database then quit the `psql`.
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_team_projects_hub TO admin;`
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_teamflow_manager TO admin;`
- `postgres=> \q`
------------

View File

@ -1,6 +1,6 @@
{
"name": "teamprojectshub",
"description": "Team Projects Hub - template backend",
"name": "teamflowmanager",
"description": "TeamFlow Manager - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",

View File

@ -0,0 +1,484 @@
"use strict";
const fs = require("fs");
const path = require("path");
const http = require("http");
const https = require("https");
const { URL } = require("url");
let CONFIG_CACHE = null;
class LocalAIApi {
static createResponse(params, options) {
return createResponse(params, options);
}
static request(pathValue, payload, options) {
return request(pathValue, payload, options);
}
static fetchStatus(aiRequestId, options) {
return fetchStatus(aiRequestId, options);
}
static awaitResponse(aiRequestId, options) {
return awaitResponse(aiRequestId, options);
}
static extractText(response) {
return extractText(response);
}
static decodeJsonFromResponse(response) {
return decodeJsonFromResponse(response);
}
}
async function createResponse(params, options = {}) {
const payload = { ...(params || {}) };
if (!Array.isArray(payload.input) || payload.input.length === 0) {
return {
success: false,
error: "input_missing",
message: 'Parameter "input" is required and must be a non-empty array.',
};
}
const cfg = config();
if (!payload.model) {
payload.model = cfg.defaultModel;
}
const initial = await request(options.path, payload, options);
if (!initial.success) {
return initial;
}
const data = initial.data;
if (data && typeof data === "object" && data.ai_request_id) {
const pollTimeout = Number(options.poll_timeout ?? 300);
const pollInterval = Number(options.poll_interval ?? 5);
return await awaitResponse(data.ai_request_id, {
interval: pollInterval,
timeout: pollTimeout,
headers: options.headers,
timeout_per_call: options.timeout,
verify_tls: options.verify_tls,
});
}
return initial;
}
async function request(pathValue, payload = {}, options = {}) {
const cfg = config();
const resolvedPath = pathValue || options.path || cfg.responsesPath;
if (!resolvedPath) {
return {
success: false,
error: "project_id_missing",
message: "PROJECT_ID is not defined; cannot resolve AI proxy endpoint.",
};
}
if (!cfg.projectUuid) {
return {
success: false,
error: "project_uuid_missing",
message: "PROJECT_UUID is not defined; aborting AI request.",
};
}
const bodyPayload = { ...(payload || {}) };
if (!bodyPayload.project_uuid) {
bodyPayload.project_uuid = cfg.projectUuid;
}
const url = buildUrl(resolvedPath, cfg.baseUrl);
const timeout = resolveTimeout(options.timeout, cfg.timeout);
const verifyTls = resolveVerifyTls(options.verify_tls, cfg.verifyTls);
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
[cfg.projectHeader]: cfg.projectUuid,
};
if (Array.isArray(options.headers)) {
for (const header of options.headers) {
if (typeof header === "string" && header.includes(":")) {
const [name, value] = header.split(":", 2);
headers[name.trim()] = value.trim();
}
}
}
const body = JSON.stringify(bodyPayload);
return sendRequest(url, "POST", body, headers, timeout, verifyTls);
}
async function fetchStatus(aiRequestId, options = {}) {
const cfg = config();
if (!cfg.projectUuid) {
return {
success: false,
error: "project_uuid_missing",
message: "PROJECT_UUID is not defined; aborting status check.",
};
}
const statusPath = resolveStatusPath(aiRequestId, cfg);
const url = buildUrl(statusPath, cfg.baseUrl);
const timeout = resolveTimeout(options.timeout, cfg.timeout);
const verifyTls = resolveVerifyTls(options.verify_tls, cfg.verifyTls);
const headers = {
Accept: "application/json",
[cfg.projectHeader]: cfg.projectUuid,
};
if (Array.isArray(options.headers)) {
for (const header of options.headers) {
if (typeof header === "string" && header.includes(":")) {
const [name, value] = header.split(":", 2);
headers[name.trim()] = value.trim();
}
}
}
return sendRequest(url, "GET", null, headers, timeout, verifyTls);
}
async function awaitResponse(aiRequestId, options = {}) {
const timeout = Number(options.timeout ?? 300);
const interval = Math.max(Number(options.interval ?? 5), 1);
const deadline = Date.now() + Math.max(timeout, interval) * 1000;
while (true) {
const statusResp = await fetchStatus(aiRequestId, {
headers: options.headers,
timeout: options.timeout_per_call,
verify_tls: options.verify_tls,
});
if (statusResp.success) {
const data = statusResp.data || {};
if (data && typeof data === "object") {
if (data.status === "success") {
return {
success: true,
status: 200,
data: data.response || data,
};
}
if (data.status === "failed") {
return {
success: false,
status: 500,
error: String(data.error || "AI request failed"),
data,
};
}
}
} else {
return statusResp;
}
if (Date.now() >= deadline) {
return {
success: false,
error: "timeout",
message: "Timed out waiting for AI response.",
};
}
await sleep(interval * 1000);
}
}
function extractText(response) {
const payload = response && typeof response === "object" ? response.data || response : null;
if (!payload || typeof payload !== "object") {
return "";
}
if (Array.isArray(payload.output)) {
let combined = "";
for (const item of payload.output) {
if (!item || !Array.isArray(item.content)) {
continue;
}
for (const block of item.content) {
if (
block &&
typeof block === "object" &&
block.type === "output_text" &&
typeof block.text === "string" &&
block.text.length > 0
) {
combined += block.text;
}
}
}
if (combined) {
return combined;
}
}
if (
payload.choices &&
payload.choices[0] &&
payload.choices[0].message &&
typeof payload.choices[0].message.content === "string"
) {
return payload.choices[0].message.content;
}
return "";
}
function decodeJsonFromResponse(response) {
const text = extractText(response);
if (!text) {
throw new Error("No text found in AI response.");
}
const parsed = parseJson(text);
if (parsed.ok && parsed.value && typeof parsed.value === "object") {
return parsed.value;
}
const stripped = stripJsonFence(text);
if (stripped !== text) {
const parsedStripped = parseJson(stripped);
if (parsedStripped.ok && parsedStripped.value && typeof parsedStripped.value === "object") {
return parsedStripped.value;
}
throw new Error(`JSON parse failed after stripping fences: ${parsedStripped.error}`);
}
throw new Error(`JSON parse failed: ${parsed.error}`);
}
function config() {
if (CONFIG_CACHE) {
return CONFIG_CACHE;
}
ensureEnvLoaded();
const baseUrl = process.env.AI_PROXY_BASE_URL || "https://flatlogic.com";
const projectId = process.env.PROJECT_ID || null;
let responsesPath = process.env.AI_RESPONSES_PATH || null;
if (!responsesPath && projectId) {
responsesPath = `/projects/${projectId}/ai-request`;
}
const timeout = resolveTimeout(process.env.AI_TIMEOUT, 30);
const verifyTls = resolveVerifyTls(process.env.AI_VERIFY_TLS, true);
CONFIG_CACHE = {
baseUrl,
responsesPath,
projectId,
projectUuid: process.env.PROJECT_UUID || null,
projectHeader: process.env.AI_PROJECT_HEADER || "project-uuid",
defaultModel: process.env.AI_DEFAULT_MODEL || "gpt-5-mini",
timeout,
verifyTls,
};
return CONFIG_CACHE;
}
function buildUrl(pathValue, baseUrl) {
const trimmed = String(pathValue || "").trim();
if (trimmed === "") {
return baseUrl;
}
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
return trimmed;
}
if (trimmed.startsWith("/")) {
return `${baseUrl}${trimmed}`;
}
return `${baseUrl}/${trimmed}`;
}
function resolveStatusPath(aiRequestId, cfg) {
const basePath = (cfg.responsesPath || "").replace(/\/+$/, "");
if (!basePath) {
return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`;
}
const normalized = basePath.endsWith("/ai-request") ? basePath : `${basePath}/ai-request`;
return `${normalized}/${encodeURIComponent(String(aiRequestId))}/status`;
}
function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls) {
return new Promise((resolve) => {
let targetUrl;
try {
targetUrl = new URL(urlString);
} catch (err) {
resolve({
success: false,
error: "invalid_url",
message: err.message,
});
return;
}
const isHttps = targetUrl.protocol === "https:";
const requestFn = isHttps ? https.request : http.request;
const options = {
protocol: targetUrl.protocol,
hostname: targetUrl.hostname,
port: targetUrl.port || (isHttps ? 443 : 80),
path: `${targetUrl.pathname}${targetUrl.search}`,
method: method.toUpperCase(),
headers,
timeout: Math.max(Number(timeoutSeconds || 30), 1) * 1000,
};
if (isHttps) {
options.rejectUnauthorized = Boolean(verifyTls);
}
const req = requestFn(options, (res) => {
let responseBody = "";
res.setEncoding("utf8");
res.on("data", (chunk) => {
responseBody += chunk;
});
res.on("end", () => {
const status = res.statusCode || 0;
const parsed = parseJson(responseBody);
const payload = parsed.ok ? parsed.value : responseBody;
if (status >= 200 && status < 300) {
const result = {
success: true,
status,
data: payload,
};
if (!parsed.ok) {
result.json_error = parsed.error;
}
resolve(result);
return;
}
const errorMessage =
parsed.ok && payload && typeof payload === "object"
? String(payload.error || payload.message || "AI proxy request failed")
: String(responseBody || "AI proxy request failed");
resolve({
success: false,
status,
error: errorMessage,
response: payload,
json_error: parsed.ok ? undefined : parsed.error,
});
});
});
req.on("timeout", () => {
req.destroy(new Error("request_timeout"));
});
req.on("error", (err) => {
resolve({
success: false,
error: "request_failed",
message: err.message,
});
});
if (body) {
req.write(body);
}
req.end();
});
}
function parseJson(value) {
if (typeof value !== "string" || value.trim() === "") {
return { ok: false, error: "empty_response" };
}
try {
return { ok: true, value: JSON.parse(value) };
} catch (err) {
return { ok: false, error: err.message };
}
}
function stripJsonFence(text) {
const trimmed = text.trim();
if (trimmed.startsWith("```json")) {
return trimmed.replace(/^```json/, "").replace(/```$/, "").trim();
}
if (trimmed.startsWith("```")) {
return trimmed.replace(/^```/, "").replace(/```$/, "").trim();
}
return text;
}
function resolveTimeout(value, fallback) {
const parsed = Number.parseInt(String(value ?? fallback), 10);
return Number.isNaN(parsed) ? Number(fallback) : parsed;
}
function resolveVerifyTls(value, fallback) {
if (value === undefined || value === null) {
return Boolean(fallback);
}
return String(value).toLowerCase() !== "false" && String(value) !== "0";
}
function ensureEnvLoaded() {
if (process.env.PROJECT_UUID && process.env.PROJECT_ID) {
return;
}
const envPath = path.resolve(__dirname, "../../../../.env");
if (!fs.existsSync(envPath)) {
return;
}
let content;
try {
content = fs.readFileSync(envPath, "utf8");
} catch (err) {
throw new Error(`Failed to read executor .env: ${err.message}`);
}
for (const line of content.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
continue;
}
const [rawKey, ...rest] = trimmed.split("=");
const key = rawKey.trim();
if (!key) {
continue;
}
const value = rest.join("=").trim().replace(/^['"]|['"]$/g, "");
if (!process.env[key]) {
process.env[key] = value;
}
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
module.exports = {
LocalAIApi,
createResponse,
request,
fetchStatus,
awaitResponse,
extractText,
decodeJsonFromResponse,
};

View File

@ -11,15 +11,15 @@ const config = {
bcrypt: {
saltRounds: 12
},
admin_pass: "d02fa926",
user_pass: "b27b3ff8dc07",
admin_pass: "156fca4c",
user_pass: "94f5514e3457",
admin_email: "admin@flatlogic.com",
providers: {
LOCAL: 'local',
GOOGLE: 'google',
MICROSOFT: 'microsoft'
},
secret_key: process.env.SECRET_KEY || 'd02fa926-6491-481e-a115-b27b3ff8dc07',
secret_key: process.env.SECRET_KEY || '156fca4c-57d0-4fe3-8a0f-94f5514e3457',
remote: '',
port: process.env.NODE_ENV === "production" ? "" : "8080",
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
@ -39,7 +39,7 @@ const config = {
},
uploadDir: os.tmpdir(),
email: {
from: 'Team Projects Hub <app@flatlogic.app>',
from: 'TeamFlow Manager <app@flatlogic.app>',
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
@ -60,7 +60,7 @@ const config = {
},
project_uuid: 'd02fa926-6491-481e-a115-b27b3ff8dc07',
project_uuid: '156fca4c-57d0-4fe3-8a0f-94f5514e3457',
flHost: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',

View File

@ -1,490 +0,0 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class AttachmentsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const attachments = await db.attachments.create(
{
id: data.id || undefined,
caption: data.caption
||
null
,
uploaded_on: data.uploaded_on
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await attachments.setTask( data.task || null, {
transaction,
});
await attachments.setUploaded_by( data.uploaded_by || null, {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.attachments.getTableName(),
belongsToColumn: 'file',
belongsToId: attachments.id,
},
data.file,
options,
);
return attachments;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const attachmentsData = data.map((item, index) => ({
id: item.id || undefined,
caption: item.caption
||
null
,
uploaded_on: item.uploaded_on
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const attachments = await db.attachments.bulkCreate(attachmentsData, { transaction });
// For each item created, replace relation files
for (let i = 0; i < attachments.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.attachments.getTableName(),
belongsToColumn: 'file',
belongsToId: attachments[i].id,
},
data[i].file,
options,
);
}
return attachments;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const attachments = await db.attachments.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.caption !== undefined) updatePayload.caption = data.caption;
if (data.uploaded_on !== undefined) updatePayload.uploaded_on = data.uploaded_on;
updatePayload.updatedById = currentUser.id;
await attachments.update(updatePayload, {transaction});
if (data.task !== undefined) {
await attachments.setTask(
data.task,
{ transaction }
);
}
if (data.uploaded_by !== undefined) {
await attachments.setUploaded_by(
data.uploaded_by,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.attachments.getTableName(),
belongsToColumn: 'file',
belongsToId: attachments.id,
},
data.file,
options,
);
return attachments;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const attachments = await db.attachments.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of attachments) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of attachments) {
await record.destroy({transaction});
}
});
return attachments;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const attachments = await db.attachments.findByPk(id, options);
await attachments.update({
deletedBy: currentUser.id
}, {
transaction,
});
await attachments.destroy({
transaction
});
return attachments;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const attachments = await db.attachments.findOne(
{ where },
{ transaction },
);
if (!attachments) {
return attachments;
}
const output = attachments.get({plain: true});
output.task = await attachments.getTask({
transaction
});
output.uploaded_by = await attachments.getUploaded_by({
transaction
});
output.file = await attachments.getFile({
transaction
});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.tasks,
as: 'task',
where: filter.task ? {
[Op.or]: [
{ id: { [Op.in]: filter.task.split('|').map(term => Utils.uuid(term)) } },
{
title: {
[Op.or]: filter.task.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.users,
as: 'uploaded_by',
where: filter.uploaded_by ? {
[Op.or]: [
{ id: { [Op.in]: filter.uploaded_by.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.uploaded_by.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.file,
as: 'file',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.caption) {
where = {
...where,
[Op.and]: Utils.ilike(
'attachments',
'caption',
filter.caption,
),
};
}
if (filter.uploaded_onRange) {
const [start, end] = filter.uploaded_onRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
uploaded_on: {
...where.uploaded_on,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
uploaded_on: {
...where.uploaded_on,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.attachments.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'attachments',
'caption',
query,
),
],
};
}
const records = await db.attachments.findAll({
attributes: [ 'id', 'caption' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['caption', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.caption,
}));
}
};

View File

@ -1,448 +0,0 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class CommentsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const comments = await db.comments.create(
{
id: data.id || undefined,
content: data.content
||
null
,
created_on: data.created_on
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await comments.setTask( data.task || null, {
transaction,
});
await comments.setAuthor( data.author || null, {
transaction,
});
return comments;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const commentsData = data.map((item, index) => ({
id: item.id || undefined,
content: item.content
||
null
,
created_on: item.created_on
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const comments = await db.comments.bulkCreate(commentsData, { transaction });
// For each item created, replace relation files
return comments;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const comments = await db.comments.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.content !== undefined) updatePayload.content = data.content;
if (data.created_on !== undefined) updatePayload.created_on = data.created_on;
updatePayload.updatedById = currentUser.id;
await comments.update(updatePayload, {transaction});
if (data.task !== undefined) {
await comments.setTask(
data.task,
{ transaction }
);
}
if (data.author !== undefined) {
await comments.setAuthor(
data.author,
{ transaction }
);
}
return comments;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const comments = await db.comments.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of comments) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of comments) {
await record.destroy({transaction});
}
});
return comments;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const comments = await db.comments.findByPk(id, options);
await comments.update({
deletedBy: currentUser.id
}, {
transaction,
});
await comments.destroy({
transaction
});
return comments;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const comments = await db.comments.findOne(
{ where },
{ transaction },
);
if (!comments) {
return comments;
}
const output = comments.get({plain: true});
output.task = await comments.getTask({
transaction
});
output.author = await comments.getAuthor({
transaction
});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.tasks,
as: 'task',
where: filter.task ? {
[Op.or]: [
{ id: { [Op.in]: filter.task.split('|').map(term => Utils.uuid(term)) } },
{
title: {
[Op.or]: filter.task.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.users,
as: 'author',
where: filter.author ? {
[Op.or]: [
{ id: { [Op.in]: filter.author.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.author.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.content) {
where = {
...where,
[Op.and]: Utils.ilike(
'comments',
'content',
filter.content,
),
};
}
if (filter.created_onRange) {
const [start, end] = filter.created_onRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
created_on: {
...where.created_on,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
created_on: {
...where.created_on,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.comments.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'comments',
'content',
query,
),
],
};
}
const records = await db.comments.findAll({
attributes: [ 'id', 'content' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['content', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.content,
}));
}
};

View File

@ -1,470 +0,0 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class NotificationsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const notifications = await db.notifications.create(
{
id: data.id || undefined,
message: data.message
||
null
,
read: data.read
||
false
,
sent_at: data.sent_at
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await notifications.setRecipient( data.recipient || null, {
transaction,
});
await notifications.setRelated_task( data.related_task || null, {
transaction,
});
return notifications;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const notificationsData = data.map((item, index) => ({
id: item.id || undefined,
message: item.message
||
null
,
read: item.read
||
false
,
sent_at: item.sent_at
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const notifications = await db.notifications.bulkCreate(notificationsData, { transaction });
// For each item created, replace relation files
return notifications;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const notifications = await db.notifications.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.message !== undefined) updatePayload.message = data.message;
if (data.read !== undefined) updatePayload.read = data.read;
if (data.sent_at !== undefined) updatePayload.sent_at = data.sent_at;
updatePayload.updatedById = currentUser.id;
await notifications.update(updatePayload, {transaction});
if (data.recipient !== undefined) {
await notifications.setRecipient(
data.recipient,
{ transaction }
);
}
if (data.related_task !== undefined) {
await notifications.setRelated_task(
data.related_task,
{ transaction }
);
}
return notifications;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const notifications = await db.notifications.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of notifications) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of notifications) {
await record.destroy({transaction});
}
});
return notifications;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const notifications = await db.notifications.findByPk(id, options);
await notifications.update({
deletedBy: currentUser.id
}, {
transaction,
});
await notifications.destroy({
transaction
});
return notifications;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const notifications = await db.notifications.findOne(
{ where },
{ transaction },
);
if (!notifications) {
return notifications;
}
const output = notifications.get({plain: true});
output.recipient = await notifications.getRecipient({
transaction
});
output.related_task = await notifications.getRelated_task({
transaction
});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.users,
as: 'recipient',
where: filter.recipient ? {
[Op.or]: [
{ id: { [Op.in]: filter.recipient.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.recipient.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.tasks,
as: 'related_task',
where: filter.related_task ? {
[Op.or]: [
{ id: { [Op.in]: filter.related_task.split('|').map(term => Utils.uuid(term)) } },
{
title: {
[Op.or]: filter.related_task.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.message) {
where = {
...where,
[Op.and]: Utils.ilike(
'notifications',
'message',
filter.message,
),
};
}
if (filter.sent_atRange) {
const [start, end] = filter.sent_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
sent_at: {
...where.sent_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
sent_at: {
...where.sent_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.read) {
where = {
...where,
read: filter.read,
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.notifications.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'notifications',
'message',
query,
),
],
};
}
const records = await db.notifications.findAll({
attributes: [ 'id', 'message' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['message', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.message,
}));
}
};

View File

@ -172,10 +172,6 @@ module.exports = class PermissionsDBApi {
return output;
}

View File

@ -31,6 +31,11 @@ module.exports = class ProjectsDBApi {
null
,
status: data.status
||
null
,
start_date: data.start_date
||
null
@ -41,18 +46,8 @@ module.exports = class ProjectsDBApi {
null
,
status: data.status
||
null
,
budget: data.budget
||
null
,
progress: data.progress
||
null
,
@ -76,6 +71,16 @@ module.exports = class ProjectsDBApi {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.projects.getTableName(),
belongsToColumn: 'attachments',
belongsToId: projects.id,
},
data.attachments,
options,
);
return projects;
}
@ -97,6 +102,11 @@ module.exports = class ProjectsDBApi {
description: item.description
||
null
,
status: item.status
||
null
,
start_date: item.start_date
@ -107,21 +117,11 @@ module.exports = class ProjectsDBApi {
end_date: item.end_date
||
null
,
status: item.status
||
null
,
budget: item.budget
||
null
,
progress: item.progress
||
null
,
importHash: item.importHash || null,
@ -135,6 +135,18 @@ module.exports = class ProjectsDBApi {
// For each item created, replace relation files
for (let i = 0; i < projects.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.projects.getTableName(),
belongsToColumn: 'attachments',
belongsToId: projects[i].id,
},
data[i].attachments,
options,
);
}
return projects;
}
@ -157,21 +169,18 @@ module.exports = class ProjectsDBApi {
if (data.description !== undefined) updatePayload.description = data.description;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.start_date !== undefined) updatePayload.start_date = data.start_date;
if (data.end_date !== undefined) updatePayload.end_date = data.end_date;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.budget !== undefined) updatePayload.budget = data.budget;
if (data.progress !== undefined) updatePayload.progress = data.progress;
updatePayload.updatedById = currentUser.id;
await projects.update(updatePayload, {transaction});
@ -201,6 +210,16 @@ module.exports = class ProjectsDBApi {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.projects.getTableName(),
belongsToColumn: 'attachments',
belongsToId: projects.id,
},
data.attachments,
options,
);
return projects;
}
@ -280,14 +299,6 @@ module.exports = class ProjectsDBApi {
output.reports_project = await projects.getReports_project({
transaction
});
output.owner = await projects.getOwner({
transaction
});
@ -298,6 +309,11 @@ module.exports = class ProjectsDBApi {
});
output.attachments = await projects.getAttachments({
transaction
});
return output;
}
@ -359,6 +375,11 @@ module.exports = class ProjectsDBApi {
{
model: db.file,
as: 'attachments',
},
];
if (filter) {
@ -487,30 +508,6 @@ module.exports = class ProjectsDBApi {
}
}
if (filter.progressRange) {
const [start, end] = filter.progressRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
progress: {
...where.progress,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
progress: {
...where.progress,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {

View File

@ -1,514 +0,0 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class ReportsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const reports = await db.reports.create(
{
id: data.id || undefined,
title: data.title
||
null
,
created_on: data.created_on
||
null
,
summary: data.summary
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await reports.setProject( data.project || null, {
transaction,
});
await reports.setGenerated_by( data.generated_by || null, {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.reports.getTableName(),
belongsToColumn: 'content_file',
belongsToId: reports.id,
},
data.content_file,
options,
);
return reports;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const reportsData = data.map((item, index) => ({
id: item.id || undefined,
title: item.title
||
null
,
created_on: item.created_on
||
null
,
summary: item.summary
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const reports = await db.reports.bulkCreate(reportsData, { transaction });
// For each item created, replace relation files
for (let i = 0; i < reports.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.reports.getTableName(),
belongsToColumn: 'content_file',
belongsToId: reports[i].id,
},
data[i].content_file,
options,
);
}
return reports;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const reports = await db.reports.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.title !== undefined) updatePayload.title = data.title;
if (data.created_on !== undefined) updatePayload.created_on = data.created_on;
if (data.summary !== undefined) updatePayload.summary = data.summary;
updatePayload.updatedById = currentUser.id;
await reports.update(updatePayload, {transaction});
if (data.project !== undefined) {
await reports.setProject(
data.project,
{ transaction }
);
}
if (data.generated_by !== undefined) {
await reports.setGenerated_by(
data.generated_by,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.reports.getTableName(),
belongsToColumn: 'content_file',
belongsToId: reports.id,
},
data.content_file,
options,
);
return reports;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const reports = await db.reports.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of reports) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of reports) {
await record.destroy({transaction});
}
});
return reports;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const reports = await db.reports.findByPk(id, options);
await reports.update({
deletedBy: currentUser.id
}, {
transaction,
});
await reports.destroy({
transaction
});
return reports;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const reports = await db.reports.findOne(
{ where },
{ transaction },
);
if (!reports) {
return reports;
}
const output = reports.get({plain: true});
output.project = await reports.getProject({
transaction
});
output.generated_by = await reports.getGenerated_by({
transaction
});
output.content_file = await reports.getContent_file({
transaction
});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.projects,
as: 'project',
where: filter.project ? {
[Op.or]: [
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.users,
as: 'generated_by',
where: filter.generated_by ? {
[Op.or]: [
{ id: { [Op.in]: filter.generated_by.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.generated_by.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.file,
as: 'content_file',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.title) {
where = {
...where,
[Op.and]: Utils.ilike(
'reports',
'title',
filter.title,
),
};
}
if (filter.summary) {
where = {
...where,
[Op.and]: Utils.ilike(
'reports',
'summary',
filter.summary,
),
};
}
if (filter.created_onRange) {
const [start, end] = filter.created_onRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
created_on: {
...where.created_on,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
created_on: {
...where.created_on,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.reports.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'reports',
'title',
query,
),
],
};
}
const records = await db.reports.findAll({
attributes: [ 'id', 'title' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['title', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.title,
}));
}
};

View File

@ -197,10 +197,6 @@ module.exports = class RolesDBApi {
output.permissions = await roles.getPermissions({
transaction
});

View File

@ -41,21 +41,26 @@ module.exports = class TasksDBApi {
null
,
due_date: data.due_date
||
null
,
start_date: data.start_date
||
null
,
due_date: data.due_date
||
null
,
estimated_hours: data.estimated_hours
||
null
,
spent_hours: data.spent_hours
||
null
,
completed: data.completed
||
false
@ -70,6 +75,10 @@ module.exports = class TasksDBApi {
);
await tasks.setProject( data.project || null, {
transaction,
});
await tasks.setAssignee( data.assignee || null, {
transaction,
});
@ -78,14 +87,20 @@ module.exports = class TasksDBApi {
transaction,
});
await tasks.setProject( data.project || null, {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.tasks.getTableName(),
belongsToColumn: 'attachments',
belongsToId: tasks.id,
},
data.attachments,
options,
);
return tasks;
}
@ -117,21 +132,26 @@ module.exports = class TasksDBApi {
priority: item.priority
||
null
,
due_date: item.due_date
||
null
,
start_date: item.start_date
||
null
,
due_date: item.due_date
||
null
,
estimated_hours: item.estimated_hours
||
null
,
spent_hours: item.spent_hours
||
null
,
completed: item.completed
@ -151,6 +171,18 @@ module.exports = class TasksDBApi {
// For each item created, replace relation files
for (let i = 0; i < tasks.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.tasks.getTableName(),
belongsToColumn: 'attachments',
belongsToId: tasks[i].id,
},
data[i].attachments,
options,
);
}
return tasks;
}
@ -179,15 +211,18 @@ module.exports = class TasksDBApi {
if (data.priority !== undefined) updatePayload.priority = data.priority;
if (data.due_date !== undefined) updatePayload.due_date = data.due_date;
if (data.start_date !== undefined) updatePayload.start_date = data.start_date;
if (data.due_date !== undefined) updatePayload.due_date = data.due_date;
if (data.estimated_hours !== undefined) updatePayload.estimated_hours = data.estimated_hours;
if (data.spent_hours !== undefined) updatePayload.spent_hours = data.spent_hours;
if (data.completed !== undefined) updatePayload.completed = data.completed;
@ -197,6 +232,15 @@ module.exports = class TasksDBApi {
if (data.project !== undefined) {
await tasks.setProject(
data.project,
{ transaction }
);
}
if (data.assignee !== undefined) {
await tasks.setAssignee(
@ -215,20 +259,21 @@ module.exports = class TasksDBApi {
);
}
if (data.project !== undefined) {
await tasks.setProject(
data.project,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.tasks.getTableName(),
belongsToColumn: 'attachments',
belongsToId: tasks.id,
},
data.attachments,
options,
);
return tasks;
}
@ -303,23 +348,12 @@ module.exports = class TasksDBApi {
output.comments_task = await tasks.getComments_task({
output.project = await tasks.getProject({
transaction
});
output.attachments_task = await tasks.getAttachments_task({
transaction
});
output.notifications_related_task = await tasks.getNotifications_related_task({
transaction
});
output.assignee = await tasks.getAssignee({
transaction
});
@ -330,7 +364,7 @@ module.exports = class TasksDBApi {
});
output.project = await tasks.getProject({
output.attachments = await tasks.getAttachments({
transaction
});
@ -360,6 +394,23 @@ module.exports = class TasksDBApi {
let include = [
{
model: db.projects,
as: 'project',
where: filter.project ? {
[Op.or]: [
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.users,
as: 'assignee',
@ -394,25 +445,13 @@ module.exports = class TasksDBApi {
},
{
model: db.projects,
as: 'project',
where: filter.project ? {
[Op.or]: [
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
model: db.file,
as: 'attachments',
},
];
if (filter) {
@ -451,30 +490,6 @@ module.exports = class TasksDBApi {
if (filter.due_dateRange) {
const [start, end] = filter.due_dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
due_date: {
...where.due_date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
due_date: {
...where.due_date,
[Op.lte]: end,
},
};
}
}
if (filter.start_dateRange) {
const [start, end] = filter.start_dateRange;
@ -499,6 +514,30 @@ module.exports = class TasksDBApi {
}
}
if (filter.due_dateRange) {
const [start, end] = filter.due_dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
due_date: {
...where.due_date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
due_date: {
...where.due_date,
[Op.lte]: end,
},
};
}
}
if (filter.estimated_hoursRange) {
const [start, end] = filter.estimated_hoursRange;
@ -523,6 +562,30 @@ module.exports = class TasksDBApi {
}
}
if (filter.spent_hoursRange) {
const [start, end] = filter.spent_hoursRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
spent_hours: {
...where.spent_hours,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
spent_hours: {
...where.spent_hours,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Team_membersDBApi {
module.exports = class Team_membershipsDBApi {
@ -17,22 +17,17 @@ module.exports = class Team_membersDBApi {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const team_members = await db.team_members.create(
const team_memberships = await db.team_memberships.create(
{
id: data.id || undefined,
member_label: data.member_label
||
null
,
role: data.role
||
null
,
joined_on: data.joined_on
||
null
,
display_label: data.display_label
||
null
,
@ -44,11 +39,11 @@ module.exports = class Team_membersDBApi {
);
await team_members.setTeam( data.team || null, {
await team_memberships.setTeam( data.team || null, {
transaction,
});
await team_members.setUser( data.user || null, {
await team_memberships.setUser( data.user || null, {
transaction,
});
@ -57,7 +52,7 @@ module.exports = class Team_membersDBApi {
return team_members;
return team_memberships;
}
@ -66,22 +61,17 @@ module.exports = class Team_membersDBApi {
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const team_membersData = data.map((item, index) => ({
const team_membershipsData = data.map((item, index) => ({
id: item.id || undefined,
member_label: item.member_label
||
null
,
role: item.role
||
null
,
joined_on: item.joined_on
||
null
,
display_label: item.display_label
||
null
,
importHash: item.importHash || null,
@ -91,12 +81,12 @@ module.exports = class Team_membersDBApi {
}));
// Bulk create items
const team_members = await db.team_members.bulkCreate(team_membersData, { transaction });
const team_memberships = await db.team_memberships.bulkCreate(team_membershipsData, { transaction });
// For each item created, replace relation files
return team_members;
return team_memberships;
}
static async update(id, data, options) {
@ -104,30 +94,27 @@ module.exports = class Team_membersDBApi {
const transaction = (options && options.transaction) || undefined;
const team_members = await db.team_members.findByPk(id, {}, {transaction});
const team_memberships = await db.team_memberships.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.member_label !== undefined) updatePayload.member_label = data.member_label;
if (data.role !== undefined) updatePayload.role = data.role;
if (data.joined_on !== undefined) updatePayload.joined_on = data.joined_on;
if (data.display_label !== undefined) updatePayload.display_label = data.display_label;
updatePayload.updatedById = currentUser.id;
await team_members.update(updatePayload, {transaction});
await team_memberships.update(updatePayload, {transaction});
if (data.team !== undefined) {
await team_members.setTeam(
await team_memberships.setTeam(
data.team,
@ -136,7 +123,7 @@ module.exports = class Team_membersDBApi {
}
if (data.user !== undefined) {
await team_members.setUser(
await team_memberships.setUser(
data.user,
@ -150,14 +137,14 @@ module.exports = class Team_membersDBApi {
return team_members;
return team_memberships;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const team_members = await db.team_members.findAll({
const team_memberships = await db.team_memberships.findAll({
where: {
id: {
[Op.in]: ids,
@ -167,53 +154,53 @@ module.exports = class Team_membersDBApi {
});
await db.sequelize.transaction(async (transaction) => {
for (const record of team_members) {
for (const record of team_memberships) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of team_members) {
for (const record of team_memberships) {
await record.destroy({transaction});
}
});
return team_members;
return team_memberships;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const team_members = await db.team_members.findByPk(id, options);
const team_memberships = await db.team_memberships.findByPk(id, options);
await team_members.update({
await team_memberships.update({
deletedBy: currentUser.id
}, {
transaction,
});
await team_members.destroy({
await team_memberships.destroy({
transaction
});
return team_members;
return team_memberships;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const team_members = await db.team_members.findOne(
const team_memberships = await db.team_memberships.findOne(
{ where },
{ transaction },
);
if (!team_members) {
return team_members;
if (!team_memberships) {
return team_memberships;
}
const output = team_members.get({plain: true});
const output = team_memberships.get({plain: true});
@ -224,16 +211,12 @@ module.exports = class Team_membersDBApi {
output.team = await team_members.getTeam({
output.team = await team_memberships.getTeam({
transaction
});
output.user = await team_members.getUser({
output.user = await team_memberships.getUser({
transaction
});
@ -310,13 +293,13 @@ module.exports = class Team_membersDBApi {
}
if (filter.display_label) {
if (filter.member_label) {
where = {
...where,
[Op.and]: Utils.ilike(
'team_members',
'display_label',
filter.display_label,
'team_memberships',
'member_label',
filter.member_label,
),
};
}
@ -326,30 +309,6 @@ module.exports = class Team_membersDBApi {
if (filter.joined_onRange) {
const [start, end] = filter.joined_onRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
joined_on: {
...where.joined_on,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
joined_on: {
...where.joined_on,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
@ -419,7 +378,7 @@ module.exports = class Team_membersDBApi {
}
try {
const { rows, count } = await db.team_members.findAndCountAll(queryOptions);
const { rows, count } = await db.team_memberships.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
@ -441,25 +400,25 @@ module.exports = class Team_membersDBApi {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'team_members',
'display_label',
'team_memberships',
'member_label',
query,
),
],
};
}
const records = await db.team_members.findAll({
attributes: [ 'id', 'display_label' ],
const records = await db.team_memberships.findAll({
attributes: [ 'id', 'member_label' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['display_label', 'ASC']],
orderBy: [['member_label', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.display_label,
label: record.member_label,
}));
}

View File

@ -28,16 +28,6 @@ module.exports = class TeamsDBApi {
description: data.description
||
null
,
visibility: data.visibility
||
null
,
members_count: data.members_count
||
null
,
@ -49,24 +39,10 @@ module.exports = class TeamsDBApi {
);
await teams.setOwner( data.owner || null, {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.teams.getTableName(),
belongsToColumn: 'avatar',
belongsToId: teams.id,
},
data.avatar,
options,
);
return teams;
}
@ -88,16 +64,6 @@ module.exports = class TeamsDBApi {
description: item.description
||
null
,
visibility: item.visibility
||
null
,
members_count: item.members_count
||
null
,
importHash: item.importHash || null,
@ -111,18 +77,6 @@ module.exports = class TeamsDBApi {
// For each item created, replace relation files
for (let i = 0; i < teams.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.teams.getTableName(),
belongsToColumn: 'avatar',
belongsToId: teams[i].id,
},
data[i].avatar,
options,
);
}
return teams;
}
@ -145,42 +99,17 @@ module.exports = class TeamsDBApi {
if (data.description !== undefined) updatePayload.description = data.description;
if (data.visibility !== undefined) updatePayload.visibility = data.visibility;
if (data.members_count !== undefined) updatePayload.members_count = data.members_count;
updatePayload.updatedById = currentUser.id;
await teams.update(updatePayload, {transaction});
if (data.owner !== undefined) {
await teams.setOwner(
data.owner,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.teams.getTableName(),
belongsToColumn: 'avatar',
belongsToId: teams.id,
},
data.avatar,
options,
);
return teams;
}
@ -252,7 +181,7 @@ module.exports = class TeamsDBApi {
output.team_members_team = await teams.getTeam_members_team({
output.team_memberships_team = await teams.getTeam_memberships_team({
transaction
});
@ -264,20 +193,6 @@ module.exports = class TeamsDBApi {
output.avatar = await teams.getAvatar({
transaction
});
output.owner = await teams.getOwner({
transaction
});
return output;
}
@ -303,30 +218,8 @@ module.exports = class TeamsDBApi {
let include = [
{
model: db.users,
as: 'owner',
where: filter.owner ? {
[Op.or]: [
{ id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.file,
as: 'avatar',
},
];
if (filter) {
@ -365,30 +258,6 @@ module.exports = class TeamsDBApi {
if (filter.members_countRange) {
const [start, end] = filter.members_countRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
members_count: {
...where.members_count,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
members_count: {
...where.members_count,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
@ -398,17 +267,8 @@ module.exports = class TeamsDBApi {
}
if (filter.visibility) {
where = {
...where,
visibility: filter.visibility,
};
}
if (filter.createdAtRange) {

View File

@ -403,12 +403,8 @@ module.exports = class UsersDBApi {
output.teams_owner = await users.getTeams_owner({
transaction
});
output.team_members_user = await users.getTeam_members_user({
output.team_memberships_user = await users.getTeam_memberships_user({
transaction
});
@ -427,26 +423,6 @@ module.exports = class UsersDBApi {
});
output.comments_author = await users.getComments_author({
transaction
});
output.attachments_uploaded_by = await users.getAttachments_uploaded_by({
transaction
});
output.notifications_recipient = await users.getNotifications_recipient({
transaction
});
output.reports_generated_by = await users.getReports_generated_by({
transaction
});
output.avatar = await users.getAvatar({
transaction

View File

@ -15,7 +15,7 @@ module.exports = {
username: 'postgres',
dialect: 'postgres',
password: '',
database: 'db_team_projects_hub',
database: 'db_teamflow_manager',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',

View File

@ -1,109 +0,0 @@
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 attachments = sequelize.define(
'attachments',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
caption: {
type: DataTypes.TEXT,
},
uploaded_on: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
attachments.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.attachments.belongsTo(db.tasks, {
as: 'task',
foreignKey: {
name: 'taskId',
},
constraints: false,
});
db.attachments.belongsTo(db.users, {
as: 'uploaded_by',
foreignKey: {
name: 'uploaded_byId',
},
constraints: false,
});
db.attachments.hasMany(db.file, {
as: 'file',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.attachments.getTableName(),
belongsToColumn: 'file',
},
});
db.attachments.belongsTo(db.users, {
as: 'createdBy',
});
db.attachments.belongsTo(db.users, {
as: 'updatedBy',
});
};
return attachments;
};

View File

@ -1,99 +0,0 @@
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 comments = sequelize.define(
'comments',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
content: {
type: DataTypes.TEXT,
},
created_on: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
comments.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.comments.belongsTo(db.tasks, {
as: 'task',
foreignKey: {
name: 'taskId',
},
constraints: false,
});
db.comments.belongsTo(db.users, {
as: 'author',
foreignKey: {
name: 'authorId',
},
constraints: false,
});
db.comments.belongsTo(db.users, {
as: 'createdBy',
});
db.comments.belongsTo(db.users, {
as: 'updatedBy',
});
};
return comments;
};

View File

@ -1,109 +0,0 @@
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 notifications = sequelize.define(
'notifications',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
message: {
type: DataTypes.TEXT,
},
read: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
sent_at: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
notifications.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.notifications.belongsTo(db.users, {
as: 'recipient',
foreignKey: {
name: 'recipientId',
},
constraints: false,
});
db.notifications.belongsTo(db.tasks, {
as: 'related_task',
foreignKey: {
name: 'related_taskId',
},
constraints: false,
});
db.notifications.belongsTo(db.users, {
as: 'createdBy',
});
db.notifications.belongsTo(db.users, {
as: 'updatedBy',
});
};
return notifications;
};

View File

@ -48,10 +48,6 @@ name: {
//end loop

View File

@ -28,6 +28,31 @@ description: {
},
status: {
type: DataTypes.ENUM,
values: [
"Draft",
"Active",
"OnHold",
"Completed",
"Cancelled"
],
},
start_date: {
type: DataTypes.DATE,
@ -42,43 +67,11 @@ end_date: {
},
status: {
type: DataTypes.ENUM,
values: [
"Planned",
"Active",
"OnHold",
"Completed",
"Archived"
],
},
budget: {
type: DataTypes.DECIMAL,
},
progress: {
type: DataTypes.INTEGER,
},
importHash: {
@ -116,18 +109,6 @@ progress: {
db.projects.hasMany(db.reports, {
as: 'reports_project',
foreignKey: {
name: 'projectId',
},
constraints: false,
});
//end loop
@ -150,6 +131,16 @@ progress: {
db.projects.hasMany(db.file, {
as: 'attachments',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.projects.getTableName(),
belongsToColumn: 'attachments',
},
});
db.projects.belongsTo(db.users, {
as: 'createdBy',

View File

@ -1,116 +0,0 @@
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 reports = sequelize.define(
'reports',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
title: {
type: DataTypes.TEXT,
},
created_on: {
type: DataTypes.DATE,
},
summary: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
reports.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.reports.belongsTo(db.projects, {
as: 'project',
foreignKey: {
name: 'projectId',
},
constraints: false,
});
db.reports.belongsTo(db.users, {
as: 'generated_by',
foreignKey: {
name: 'generated_byId',
},
constraints: false,
});
db.reports.hasMany(db.file, {
as: 'content_file',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.reports.getTableName(),
belongsToColumn: 'content_file',
},
});
db.reports.belongsTo(db.users, {
as: 'createdBy',
});
db.reports.belongsTo(db.users, {
as: 'updatedBy',
});
};
return reports;
};

View File

@ -81,10 +81,6 @@ role_customization: {
//end loop

View File

@ -35,9 +35,6 @@ status: {
values: [
"Backlog",
"ToDo",
@ -47,10 +44,10 @@ status: {
"InReview",
"Done",
"Blocked",
"Blocked"
"Done"
],
@ -78,18 +75,18 @@ priority: {
},
due_date: {
type: DataTypes.DATE,
},
start_date: {
type: DataTypes.DATE,
},
due_date: {
type: DataTypes.DATE,
},
estimated_hours: {
@ -97,6 +94,13 @@ estimated_hours: {
},
spent_hours: {
type: DataTypes.DECIMAL,
},
completed: {
@ -135,39 +139,19 @@ completed: {
db.tasks.hasMany(db.comments, {
as: 'comments_task',
foreignKey: {
name: 'taskId',
},
constraints: false,
});
db.tasks.hasMany(db.attachments, {
as: 'attachments_task',
foreignKey: {
name: 'taskId',
},
constraints: false,
});
db.tasks.hasMany(db.notifications, {
as: 'notifications_related_task',
foreignKey: {
name: 'related_taskId',
},
constraints: false,
});
//end loop
db.tasks.belongsTo(db.projects, {
as: 'project',
foreignKey: {
name: 'projectId',
},
constraints: false,
});
db.tasks.belongsTo(db.users, {
as: 'assignee',
foreignKey: {
@ -184,17 +168,19 @@ completed: {
constraints: false,
});
db.tasks.belongsTo(db.projects, {
as: 'project',
foreignKey: {
name: 'projectId',
},
db.tasks.hasMany(db.file, {
as: 'attachments',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.tasks.getTableName(),
belongsToColumn: 'attachments',
},
});
db.tasks.belongsTo(db.users, {
as: 'createdBy',
});

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const team_members = sequelize.define(
'team_members',
const team_memberships = sequelize.define(
'team_memberships',
{
id: {
type: DataTypes.UUID,
@ -14,6 +14,13 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
member_label: {
type: DataTypes.TEXT,
},
role: {
type: DataTypes.ENUM,
@ -30,20 +37,6 @@ role: {
},
joined_on: {
type: DataTypes.DATE,
},
display_label: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
@ -57,7 +50,7 @@ display_label: {
},
);
team_members.associate = (db) => {
team_memberships.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
@ -71,15 +64,11 @@ display_label: {
//end loop
db.team_members.belongsTo(db.teams, {
db.team_memberships.belongsTo(db.teams, {
as: 'team',
foreignKey: {
name: 'teamId',
@ -87,7 +76,7 @@ display_label: {
constraints: false,
});
db.team_members.belongsTo(db.users, {
db.team_memberships.belongsTo(db.users, {
as: 'user',
foreignKey: {
name: 'userId',
@ -98,18 +87,18 @@ display_label: {
db.team_members.belongsTo(db.users, {
db.team_memberships.belongsTo(db.users, {
as: 'createdBy',
});
db.team_members.belongsTo(db.users, {
db.team_memberships.belongsTo(db.users, {
as: 'updatedBy',
});
};
return team_members;
return team_memberships;
};

View File

@ -26,29 +26,6 @@ description: {
},
visibility: {
type: DataTypes.ENUM,
values: [
"Private",
"Public"
],
},
members_count: {
type: DataTypes.INTEGER,
},
importHash: {
@ -74,8 +51,8 @@ members_count: {
db.teams.hasMany(db.team_members, {
as: 'team_members_team',
db.teams.hasMany(db.team_memberships, {
as: 'team_memberships_team',
foreignKey: {
name: 'teamId',
},
@ -94,35 +71,13 @@ members_count: {
//end loop
db.teams.belongsTo(db.users, {
as: 'owner',
foreignKey: {
name: 'ownerId',
},
constraints: false,
});
db.teams.hasMany(db.file, {
as: 'avatar',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.teams.getTableName(),
belongsToColumn: 'avatar',
},
});
db.teams.belongsTo(db.users, {
as: 'createdBy',
});

View File

@ -144,17 +144,9 @@ provider: {
db.users.hasMany(db.teams, {
as: 'teams_owner',
foreignKey: {
name: 'ownerId',
},
constraints: false,
});
db.users.hasMany(db.team_members, {
as: 'team_members_user',
db.users.hasMany(db.team_memberships, {
as: 'team_memberships_user',
foreignKey: {
name: 'userId',
},
@ -188,42 +180,6 @@ provider: {
});
db.users.hasMany(db.comments, {
as: 'comments_author',
foreignKey: {
name: 'authorId',
},
constraints: false,
});
db.users.hasMany(db.attachments, {
as: 'attachments_uploaded_by',
foreignKey: {
name: 'uploaded_byId',
},
constraints: false,
});
db.users.hasMany(db.notifications, {
as: 'notifications_recipient',
foreignKey: {
name: 'recipientId',
},
constraints: false,
});
db.users.hasMany(db.reports, {
as: 'reports_generated_by',
foreignKey: {
name: 'generated_byId',
},
constraints: false,
});
//end loop

View File

@ -33,11 +33,11 @@ module.exports = {
{ id: getId("ProjectOwner"), name: "Project Owner", createdAt, updatedAt },
{ id: getId("PlatformLead"), name: "Platform Lead", createdAt, updatedAt },
{ id: getId("ProjectManager"), name: "Project Manager", createdAt, updatedAt },
{ id: getId("ProjectLead"), name: "Project Lead", createdAt, updatedAt },
{ id: getId("TeamLead"), name: "Team Lead", createdAt, updatedAt },
{ id: getId("TeamManager"), name: "Team Manager", createdAt, updatedAt },
{ id: getId("Contributor"), name: "Contributor", createdAt, updatedAt },
@ -61,7 +61,7 @@ module.exports = {
}
const entities = [
"users","roles","permissions","teams","team_members","projects","tasks","comments","attachments","notifications","reports",,
"users","roles","permissions","teams","team_memberships","projects","tasks",,
];
await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions));
await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]);
@ -90,19 +90,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('UPDATE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('DELETE_USERS') },
@ -113,12 +113,16 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('UPDATE_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('DELETE_USERS') },
@ -128,10 +132,12 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('READ_USERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('UPDATE_USERS') },
@ -183,40 +189,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('DELETE_TEAMS') },
@ -227,11 +212,34 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('CREATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('READ_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('DELETE_TEAMS') },
@ -280,61 +288,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('UPDATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('DELETE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('DELETE_TEAM_MEMBERSHIPS') },
@ -345,7 +311,45 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('UPDATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('CREATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('UPDATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('DELETE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
@ -360,7 +364,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
@ -383,19 +387,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('UPDATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('DELETE_PROJECTS') },
@ -404,19 +408,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('UPDATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('DELETE_PROJECTS') },
@ -425,15 +429,13 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('UPDATE_PROJECTS') },
@ -484,19 +486,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('DELETE_TASKS') },
@ -505,19 +507,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('DELETE_TASKS') },
@ -526,19 +528,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('CREATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('READ_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('DELETE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('DELETE_TASKS') },
@ -581,436 +583,16 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('DELETE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('CREATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('UPDATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('DELETE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('DELETE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('CREATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('UPDATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('DELETE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('UPDATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('DELETE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('UPDATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('DELETE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('UPDATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('DELETE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Viewer"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectOwner"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("PlatformLead"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectManager"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("ProjectLead"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamLead"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("TeamManager"), permissionId: getId('CREATE_SEARCH') },
{ createdAt, updatedAt, roles_permissionsId: getId("Contributor"), permissionId: getId('CREATE_SEARCH') },
@ -1039,10 +621,10 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_TEAMS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_TEAM_MEMBERS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_TEAM_MEMBERSHIPS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PROJECTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PROJECTS') },
@ -1054,26 +636,6 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_TASKS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_COMMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ATTACHMENTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_NOTIFICATIONS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_REPORTS') },
{ createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_API_DOCS') },
@ -1089,8 +651,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [
await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("ProjectOwner")}' WHERE "email"='client@hello.com'`);
await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("ProjectManager")}' WHERE "email"='john@doe.com'`);
await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("PlatformLead")}' WHERE "email"='client@hello.com'`);
await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("ProjectLead")}' WHERE "email"='john@doe.com'`);

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,10 @@ const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const pexelsRoutes = require('./routes/pexels');
const openaiRoutes = require('./routes/openai');
const usersRoutes = require('./routes/users');
const rolesRoutes = require('./routes/roles');
@ -30,20 +28,12 @@ const permissionsRoutes = require('./routes/permissions');
const teamsRoutes = require('./routes/teams');
const team_membersRoutes = require('./routes/team_members');
const team_membershipsRoutes = require('./routes/team_memberships');
const projectsRoutes = require('./routes/projects');
const tasksRoutes = require('./routes/tasks');
const commentsRoutes = require('./routes/comments');
const attachmentsRoutes = require('./routes/attachments');
const notificationsRoutes = require('./routes/notifications');
const reportsRoutes = require('./routes/reports');
const getBaseUrl = (url) => {
if (!url) return '';
@ -55,8 +45,8 @@ const options = {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "Team Projects Hub",
description: "Team Projects Hub Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
title: "TeamFlow Manager",
description: "TeamFlow Manager Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
},
servers: [
{
@ -110,27 +100,22 @@ app.use('/api/permissions', passport.authenticate('jwt', {session: false}), perm
app.use('/api/teams', passport.authenticate('jwt', {session: false}), teamsRoutes);
app.use('/api/team_members', passport.authenticate('jwt', {session: false}), team_membersRoutes);
app.use('/api/team_memberships', passport.authenticate('jwt', {session: false}), team_membershipsRoutes);
app.use('/api/projects', passport.authenticate('jwt', {session: false}), projectsRoutes);
app.use('/api/tasks', passport.authenticate('jwt', {session: false}), tasksRoutes);
app.use('/api/comments', passport.authenticate('jwt', {session: false}), commentsRoutes);
app.use('/api/attachments', passport.authenticate('jwt', {session: false}), attachmentsRoutes);
app.use('/api/notifications', passport.authenticate('jwt', {session: false}), notificationsRoutes);
app.use('/api/reports', passport.authenticate('jwt', {session: false}), reportsRoutes);
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
openaiRoutes,
);
app.use(
'/api/ai',
passport.authenticate('jwt', { session: false }),
openaiRoutes,
);
app.use(
'/api/search',

View File

@ -1,426 +0,0 @@
const express = require('express');
const AttachmentsService = require('../services/attachments');
const AttachmentsDBApi = require('../db/api/attachments');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('attachments'));
/**
* @swagger
* components:
* schemas:
* Attachments:
* type: object
* properties:
*/
/**
* @swagger
* tags:
* name: Attachments
* description: The Attachments managing API
*/
/**
* @swagger
* /api/attachments:
* post:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Attachments"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await AttachmentsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Attachments"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await AttachmentsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/attachments/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Attachments"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await AttachmentsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/attachments/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await AttachmentsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/attachments/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await AttachmentsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/attachments:
* get:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Get all attachments
* description: Get all attachments
* responses:
* 200:
* description: Attachments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await AttachmentsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/attachments/count:
* get:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Count all attachments
* description: Count all attachments
* responses:
* 200:
* description: Attachments count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await AttachmentsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/attachments/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Find all attachments that match search criteria
* description: Find all attachments that match search criteria
* responses:
* 200:
* description: Attachments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Attachments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await AttachmentsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/attachments/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Attachments]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Attachments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await AttachmentsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -1,426 +0,0 @@
const express = require('express');
const CommentsService = require('../services/comments');
const CommentsDBApi = require('../db/api/comments');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('comments'));
/**
* @swagger
* components:
* schemas:
* Comments:
* type: object
* properties:
*/
/**
* @swagger
* tags:
* name: Comments
* description: The Comments managing API
*/
/**
* @swagger
* /api/comments:
* post:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Comments"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await CommentsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Comments"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await CommentsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/comments/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Comments"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await CommentsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/comments/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await CommentsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/comments/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await CommentsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/comments:
* get:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Get all comments
* description: Get all comments
* responses:
* 200:
* description: Comments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await CommentsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/comments/count:
* get:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Count all comments
* description: Count all comments
* responses:
* 200:
* description: Comments count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await CommentsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/comments/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Find all comments that match search criteria
* description: Find all comments that match search criteria
* responses:
* 200:
* description: Comments list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Comments"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await CommentsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/comments/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Comments]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Comments"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await CommentsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -1,426 +0,0 @@
const express = require('express');
const NotificationsService = require('../services/notifications');
const NotificationsDBApi = require('../db/api/notifications');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('notifications'));
/**
* @swagger
* components:
* schemas:
* Notifications:
* type: object
* properties:
*/
/**
* @swagger
* tags:
* name: Notifications
* description: The Notifications managing API
*/
/**
* @swagger
* /api/notifications:
* post:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Notifications"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await NotificationsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Notifications"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await NotificationsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/notifications/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Notifications"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await NotificationsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/notifications/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await NotificationsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/notifications/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await NotificationsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/notifications:
* get:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Get all notifications
* description: Get all notifications
* responses:
* 200:
* description: Notifications list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await NotificationsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/notifications/count:
* get:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Count all notifications
* description: Count all notifications
* responses:
* 200:
* description: Notifications count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await NotificationsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/notifications/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Find all notifications that match search criteria
* description: Find all notifications that match search criteria
* responses:
* 200:
* description: Notifications list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Notifications"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await NotificationsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/notifications/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Notifications]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Notifications"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await NotificationsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -4,8 +4,21 @@ const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const sjs = require('sequelize-json-schema');
const { getWidget, askGpt } = require('../services/openai');
const RolesService = require('../services/roles');
const RolesDBApi = require("../db/api/roles");
const { LocalAIApi } = require('../ai/LocalAIApi');
const loadRolesModules = () => {
try {
return {
RolesService: require('../services/roles'),
RolesDBApi: require('../db/api/roles'),
};
} catch (error) {
console.error('Roles modules are missing. Advanced roles are required for this endpoint.', error);
const err = new Error('Roles modules are missing. Advanced roles are required for this endpoint.');
err.originalError = error;
throw err;
}
};
/**
* @swagger
@ -59,6 +72,7 @@ const RolesDBApi = require("../db/api/roles");
router.delete(
'/roles-info/:infoId',
wrapAsync(async (req, res) => {
const { RolesService } = loadRolesModules();
const role = await RolesService.removeRoleInfoById(
req.query.infoId,
req.query.roleId,
@ -116,6 +130,7 @@ router.delete(
router.get(
'/info-by-key',
wrapAsync(async (req, res) => {
const { RolesService, RolesDBApi } = loadRolesModules();
const roleId = req.query.roleId;
const key = req.query.key;
const currentUser = req.currentUser;
@ -124,16 +139,16 @@ router.get(
roleId,
currentUser,
);
const role = await RolesDBApi.findBy({ id: roleId });
const role = await RolesDBApi.findBy({ id: roleId });
if (!role?.role_customization) {
await Promise.all(["pie","bar"].map(async (e)=>{
await Promise.all(["pie", "bar"].map(async (e) => {
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const payload = {
description: `Create some cool ${e} chart`,
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload, currentUser?.id, roleId);
if(widgetId){
if (widgetId) {
await RolesService.addRoleInfo(
roleId,
currentUser?.id,
@ -156,6 +171,7 @@ router.get(
router.post(
'/create_widget',
wrapAsync(async (req, res) => {
const { RolesService } = loadRolesModules();
const { description, userId, roleId } = req.body;
const currentUser = req.currentUser;
@ -185,13 +201,13 @@ router.post(
/**
* @swagger
* /api/openai/ask:
* /api/openai/response:
* post:
* security:
* - bearerAuth: []
* tags: [OpenAI]
* summary: Ask a question to ChatGPT
* description: Send a question to OpenAI's ChatGPT and get a response
* summary: Proxy a Responses API request
* description: Sends the payload to the Flatlogic AI proxy and returns the response.
* requestBody:
* required: true
* content:
@ -199,12 +215,73 @@ router.post(
* schema:
* type: object
* properties:
* question:
* input:
* type: array
* description: List of messages with roles and content.
* items:
* type: object
* properties:
* role:
* type: string
* content:
* type: string
* options:
* type: object
* description: Optional polling controls.
* properties:
* poll_interval:
* type: number
* poll_timeout:
* type: number
* responses:
* 200:
* description: AI response received
* 400:
* description: Invalid request
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 502:
* description: Proxy error
*/
router.post(
'/response',
wrapAsync(async (req, res) => {
const body = req.body || {};
const options = body.options || {};
const payload = { ...body };
delete payload.options;
const response = await LocalAIApi.createResponse(payload, options);
if (response.success) {
return res.status(200).send(response);
}
console.error('AI proxy error:', response);
const status = response.error === 'input_missing' ? 400 : 502;
return res.status(status).send(response);
}),
);
/**
* @swagger
* /api/openai/ask:
* post:
* security:
* - bearerAuth: []
* tags: [OpenAI]
* summary: Ask a question to ChatGPT
* description: Send a question through the Flatlogic AI proxy and get a response
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* prompt:
* type: string
* description: The question to ask ChatGPT
* apiKey:
* type: string
* description: OpenAI API key
* responses:
* 200:
* description: Question successfully answered
@ -233,7 +310,7 @@ router.post(
if (!prompt) {
return res.status(400).send({
success: false,
error: 'Question and API key are required',
error: 'Prompt is required',
});
}

View File

@ -1,426 +0,0 @@
const express = require('express');
const ReportsService = require('../services/reports');
const ReportsDBApi = require('../db/api/reports');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('reports'));
/**
* @swagger
* components:
* schemas:
* Reports:
* type: object
* properties:
*/
/**
* @swagger
* tags:
* name: Reports
* description: The Reports managing API
*/
/**
* @swagger
* /api/reports:
* post:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Reports"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReportsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Reports"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReportsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reports/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Reports"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await ReportsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reports/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await ReportsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reports/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await ReportsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reports:
* get:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Get all reports
* description: Get all reports
* responses:
* 200:
* description: Reports list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await ReportsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/reports/count:
* get:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Count all reports
* description: Count all reports
* responses:
* 200:
* description: Reports count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await ReportsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reports/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Find all reports that match search criteria
* description: Find all reports that match search criteria
* responses:
* 200:
* description: Reports list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await ReportsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/reports/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Reports]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reports"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ReportsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -1,8 +1,8 @@
const express = require('express');
const Team_membersService = require('../services/team_members');
const Team_membersDBApi = require('../db/api/team_members');
const Team_membershipsService = require('../services/team_memberships');
const Team_membershipsDBApi = require('../db/api/team_memberships');
const wrapAsync = require('../helpers').wrapAsync;
@ -15,14 +15,14 @@ const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('team_members'));
router.use(checkCrudPermissions('team_memberships'));
/**
* @swagger
* components:
* schemas:
* Team_members:
* Team_memberships:
* type: object
* properties:
@ -34,17 +34,17 @@ router.use(checkCrudPermissions('team_members'));
/**
* @swagger
* tags:
* name: Team_members
* description: The Team_members managing API
* name: Team_memberships
* description: The Team_memberships managing API
*/
/**
* @swagger
* /api/team_members:
* /api/team_memberships:
* post:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Add new item
* description: Add new item
* requestBody:
@ -56,14 +56,14 @@ router.use(checkCrudPermissions('team_members'));
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -74,7 +74,7 @@ router.use(checkCrudPermissions('team_members'));
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Team_membersService.create(req.body.data, req.currentUser, true, link.host);
await Team_membershipsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
@ -85,7 +85,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
@ -98,14 +98,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -117,18 +117,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Team_membersService.bulkImport(req, res, true, link.host);
await Team_membershipsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/team_members/{id}:
* /api/team_memberships/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
@ -151,7 +151,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* required:
* - id
* responses:
@ -160,7 +160,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 400:
* description: Invalid ID supplied
* 401:
@ -171,18 +171,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Team_membersService.update(req.body.data, req.body.id, req.currentUser);
await Team_membershipsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/team_members/{id}:
* /api/team_memberships/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
@ -198,7 +198,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 400:
* description: Invalid ID supplied
* 401:
@ -209,18 +209,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Team_membersService.remove(req.params.id, req.currentUser);
await Team_membershipsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/team_members/deleteByIds:
* /api/team_memberships/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
@ -238,7 +238,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -247,29 +247,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Team_membersService.deleteByIds(req.body.data, req.currentUser);
await Team_membershipsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/team_members:
* /api/team_memberships:
* get:
* security:
* - bearerAuth: []
* tags: [Team_members]
* summary: Get all team_members
* description: Get all team_members
* tags: [Team_memberships]
* summary: Get all team_memberships
* description: Get all team_memberships
* responses:
* 200:
* description: Team_members list successfully received
* description: Team_memberships list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -281,7 +281,7 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Team_membersDBApi.findAll(
const payload = await Team_membershipsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
@ -307,22 +307,22 @@ router.get('/', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/team_members/count:
* /api/team_memberships/count:
* get:
* security:
* - bearerAuth: []
* tags: [Team_members]
* summary: Count all team_members
* description: Count all team_members
* tags: [Team_memberships]
* summary: Count all team_memberships
* description: Count all team_memberships
* responses:
* 200:
* description: Team_members count successfully received
* description: Team_memberships count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -333,7 +333,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Team_membersDBApi.findAll(
const payload = await Team_membershipsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
@ -344,22 +344,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/team_members/autocomplete:
* /api/team_memberships/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Team_members]
* summary: Find all team_members that match search criteria
* description: Find all team_members that match search criteria
* tags: [Team_memberships]
* summary: Find all team_memberships that match search criteria
* description: Find all team_memberships that match search criteria
* responses:
* 200:
* description: Team_members list successfully received
* description: Team_memberships list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -369,7 +369,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Team_membersDBApi.findAllAutocomplete(
const payload = await Team_membershipsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
@ -381,11 +381,11 @@ router.get('/autocomplete', async (req, res) => {
/**
* @swagger
* /api/team_members/{id}:
* /api/team_memberships/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Team_members]
* tags: [Team_memberships]
* summary: Get selected item
* description: Get selected item
* parameters:
@ -401,7 +401,7 @@ router.get('/autocomplete', async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Team_members"
* $ref: "#/components/schemas/Team_memberships"
* 400:
* description: Invalid ID supplied
* 401:
@ -412,7 +412,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Team_membersDBApi.findBy(
const payload = await Team_membershipsDBApi.findBy(
{ id: req.params.id },
);

View File

@ -1,138 +0,0 @@
const db = require('../db/models');
const AttachmentsDBApi = require('../db/api/attachments');
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');
module.exports = class AttachmentsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AttachmentsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
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', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await AttachmentsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let attachments = await AttachmentsDBApi.findBy(
{id},
{transaction},
);
if (!attachments) {
throw new ValidationError(
'attachmentsNotFound',
);
}
const updatedAttachments = await AttachmentsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedAttachments;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AttachmentsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await AttachmentsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -1,138 +0,0 @@
const db = require('../db/models');
const NotificationsDBApi = require('../db/api/notifications');
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');
module.exports = class NotificationsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await NotificationsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
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', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await NotificationsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let notifications = await NotificationsDBApi.findBy(
{id},
{transaction},
);
if (!notifications) {
throw new ValidationError(
'notificationsNotFound',
);
}
const updatedNotifications = await NotificationsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedNotifications;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await NotificationsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await NotificationsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -1,6 +1,6 @@
const errors = {
app: {
title: 'Team Projects Hub',
title: 'TeamFlow Manager',
},
auth: {

View File

@ -1,13 +1,24 @@
const axios = require('axios');
const { v4: uuid } = require('uuid');
const RoleService = require('./roles');
const config = require('../config');
const { LocalAIApi } = require('../ai/LocalAIApi');
const loadRoleService = () => {
try {
return require('./roles');
} catch (error) {
console.error('Role service is missing. Advanced roles are required for this operation.', error);
const err = new Error('Role service is missing. Advanced roles are required for this operation.');
err.originalError = error;
throw err;
}
};
module.exports = class OpenAiService {
static async getWidget(payload, userId, roleId) {
const RoleService = loadRoleService();
const response = await axios.post(
`${config.flHost}/${config.project_uuid}/project_customization_widgets.json`,
payload,
`${config.flHost}/${config.project_uuid}/project_customization_widgets.json`,
payload,
);
if (response.status >= 200 && response.status < 300) {
@ -21,47 +32,49 @@ module.exports = class OpenAiService {
}
static async askGpt(prompt) {
if (!config.gpt_key) {
if (!prompt) {
return {
success: false,
error: 'API key is required'
error: 'Prompt is required'
};
}
try {
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: 'gpt-4o',
messages: [
{ role: 'user', content: prompt }
]
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.gpt_key}`
}
}
);
if (response.status >= 200 && response.status < 300) {
return {
success: true,
data: response.data.choices[0].message.content
};
} else {
console.error('Error asking question to ChatGPT:', response.data);
return {
success: false,
error: response.data
};
const response = await LocalAIApi.createResponse(
{
input: [{ role: 'user', content: prompt }],
},
{
poll_interval: 5,
poll_timeout: 300,
},
);
if (response.success) {
let text = LocalAIApi.extractText(response);
if (!text) {
try {
const decoded = LocalAIApi.decodeJsonFromResponse(response);
text = JSON.stringify(decoded);
} catch (error) {
console.error('AI JSON decode failed:', error);
return {
success: false,
error: 'AI response parsing failed',
details: error.message || String(error),
};
}
}
} catch (error) {
console.error('Error asking question to ChatGPT:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data || error.message
success: true,
data: text,
};
}
console.error('AI proxy error:', response);
return {
success: false,
error: response.error || response.message || 'AI proxy error',
response,
};
}
};

View File

@ -1,138 +0,0 @@
const db = require('../db/models');
const ReportsDBApi = require('../db/api/reports');
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');
module.exports = class ReportsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ReportsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
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', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await ReportsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let reports = await ReportsDBApi.findBy(
{id},
{transaction},
);
if (!reports) {
throw new ValidationError(
'reportsNotFound',
);
}
const updatedReports = await ReportsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedReports;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ReportsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ReportsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -78,26 +78,6 @@ module.exports = class SearchService {
@ -122,22 +102,6 @@ module.exports = class SearchService {

View File

@ -1,138 +0,0 @@
const db = require('../db/models');
const Team_membersDBApi = require('../db/api/team_members');
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');
module.exports = class Team_membersService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Team_membersDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
await processFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
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', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Team_membersDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
currentUser: req.currentUser
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let team_members = await Team_membersDBApi.findBy(
{id},
{transaction},
);
if (!team_members) {
throw new ValidationError(
'team_membersNotFound',
);
}
const updatedTeam_members = await Team_membersDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedTeam_members;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Team_membersDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async remove(id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Team_membersDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -1,5 +1,5 @@
const db = require('../db/models');
const CommentsDBApi = require('../db/api/comments');
const Team_membershipsDBApi = require('../db/api/team_memberships');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
@ -11,11 +11,11 @@ const stream = require('stream');
module.exports = class CommentsService {
module.exports = class Team_membershipsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await CommentsDBApi.create(
await Team_membershipsDBApi.create(
data,
{
currentUser,
@ -51,7 +51,7 @@ module.exports = class CommentsService {
.on('error', (error) => reject(error));
})
await CommentsDBApi.bulkImport(results, {
await Team_membershipsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
@ -68,18 +68,18 @@ module.exports = class CommentsService {
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let comments = await CommentsDBApi.findBy(
let team_memberships = await Team_membershipsDBApi.findBy(
{id},
{transaction},
);
if (!comments) {
if (!team_memberships) {
throw new ValidationError(
'commentsNotFound',
'team_membershipsNotFound',
);
}
const updatedComments = await CommentsDBApi.update(
const updatedTeam_memberships = await Team_membershipsDBApi.update(
id,
data,
{
@ -89,7 +89,7 @@ module.exports = class CommentsService {
);
await transaction.commit();
return updatedComments;
return updatedTeam_memberships;
} catch (error) {
await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class CommentsService {
const transaction = await db.sequelize.transaction();
try {
await CommentsDBApi.deleteByIds(ids, {
await Team_membershipsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
@ -117,7 +117,7 @@ module.exports = class CommentsService {
const transaction = await db.sequelize.transaction();
try {
await CommentsDBApi.remove(
await Team_membershipsDBApi.remove(
id,
{
currentUser,

View File

@ -2,13 +2,16 @@ const chokidar = require('chokidar');
const { exec } = require('child_process');
const nodemon = require('nodemon');
const nodeEnv = 'dev_stage';
const childEnv = { ...process.env, NODE_ENV: nodeEnv };
const migrationsWatcher = chokidar.watch('./src/db/migrations', {
persistent: true,
ignoreInitial: true
});
migrationsWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New migration file: ${filePath}`);
exec('npm run db:migrate', (error, stdout, stderr) => {
exec('npm run db:migrate', { env: childEnv }, (error, stdout, stderr) => {
console.log(stdout);
if (error) {
console.error(stderr);
@ -22,7 +25,7 @@ const seedersWatcher = chokidar.watch('./src/db/seeders', {
});
seedersWatcher.on('add', (filePath) => {
console.log(`[DEBUG] New seed file: ${filePath}`);
exec('npm run db:seed', (error, stdout, stderr) => {
exec('npm run db:seed', { env: childEnv }, (error, stdout, stderr) => {
console.log(stdout);
if (error) {
console.error(stderr);
@ -32,6 +35,7 @@ seedersWatcher.on('add', (filePath) => {
nodemon({
script: './src/index.js',
env: childEnv,
ignore: ['./src/db/migrations', './src/db/seeders'],
delay: '500'
});
@ -42,4 +46,4 @@ nodemon.on('start', () => {
nodemon.on('restart', (files) => {
console.log('Nodemon restarted due changes in:', files);
});
});

View File

@ -25,7 +25,7 @@ services:
- ./data/db:/var/lib/postgresql/data
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
- POSTGRES_DB=db_team_projects_hub
- POSTGRES_DB=db_teamflow_manager
ports:
- "5432:5432"
logging:

View File

@ -1,4 +1,4 @@
# Team Projects Hub
# TeamFlow Manager
## This project was generated by Flatlogic Platform.
## Install

View File

@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./build/types/routes.d.ts" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

View File

@ -1,42 +1,49 @@
import type { ColorButtonKey } from './interfaces'
export const gradientBgBase = 'bg-gradient-to-tr'
export const colorBgBase = "bg-violet-50/50"
export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`
export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}`
export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`;
export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`
// Base background color
export const colorBgBase = 'bg-gray-100 dark:bg-gray-900'
// No more gradients
export const gradientBgBase = ''
export const gradientBgPurplePink = ''
export const gradientBgViolet = ''
export const gradientBgDark = ''
export const gradientBgPinkRed = ''
// Simplified background colors
export const colorsBgLight = {
white: 'bg-white text-black',
light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white',
white: 'bg-white text-black dark:bg-gray-800 dark:text-white',
light: 'bg-gray-100 text-black dark:bg-gray-800 dark:text-white',
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
danger: 'bg-red-500 border-red-500 text-white',
success: 'bg-green-500 border-green-500 text-white',
danger: 'bg-red-600 border-red-600 text-white',
warning: 'bg-yellow-500 border-yellow-500 text-white',
info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
info: 'bg-blue-500 border-blue-500 text-white',
}
// Simplified text colors
export const colorsText = {
white: 'text-black dark:text-slate-100',
light: 'text-gray-700 dark:text-slate-400',
white: 'text-black dark:text-gray-100',
light: 'text-gray-700 dark:text-gray-400',
contrast: 'dark:text-white',
success: 'text-emerald-500',
danger: 'text-red-500',
success: 'text-green-500',
danger: 'text-red-600',
warning: 'text-yellow-500',
info: 'text-blue-500',
};
}
// Simplified outline colors
export const colorsOutline = {
white: [colorsText.white, 'border-gray-100'].join(' '),
light: [colorsText.light, 'border-gray-100'].join(' '),
contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(' '),
success: [colorsText.success, 'border-emerald-500'].join(' '),
danger: [colorsText.danger, 'border-red-500'].join(' '),
white: [colorsText.white, 'border-gray-200 dark:border-gray-700'].join(' '),
light: [colorsText.light, 'border-gray-200 dark:border-gray-700'].join(' '),
contrast: [colorsText.contrast, 'border-gray-700 dark:border-gray-200'].join(' '),
success: [colorsText.success, 'border-green-500'].join(' '),
danger: [colorsText.danger, 'border-red-600'].join(' '),
warning: [colorsText.warning, 'border-yellow-500'].join(' '),
info: [colorsText.info, 'border-blue-500'].join(' '),
};
}
// Simplified button color logic
export const getButtonColor = (
color: ColorButtonKey,
isOutlined: boolean,
@ -47,92 +54,104 @@ export const getButtonColor = (
return ''
}
const colors = {
ring: {
white: 'ring-gray-200 dark:ring-gray-500',
whiteDark: 'ring-gray-200 dark:ring-dark-500',
lightDark: 'ring-gray-200 dark:ring-gray-500',
contrast: 'ring-gray-300 dark:ring-gray-400',
success: 'ring-emerald-300 dark:ring-pavitra-blue',
danger: 'ring-red-300 dark:ring-red-700',
warning: 'ring-yellow-300 dark:ring-yellow-700',
info: "ring-blue-300 dark:ring-pavitra-blue",
const baseColors = {
white: {
bg: 'bg-white text-black',
border: 'border-gray-300',
hover: 'hover:bg-gray-50',
active: 'bg-gray-100',
ring: 'ring-gray-300',
},
active: {
white: 'bg-gray-100',
whiteDark: 'bg-gray-100 dark:bg-dark-800',
lightDark: 'bg-gray-200 dark:bg-slate-700',
contrast: 'bg-gray-700 dark:bg-slate-100',
success: 'bg-emerald-700 dark:bg-pavitra-blue',
danger: 'bg-red-700 dark:bg-red-600',
warning: 'bg-yellow-700 dark:bg-yellow-600',
info: 'bg-blue-700 dark:bg-pavitra-blue',
light: {
bg: 'bg-gray-200 text-black',
border: 'border-gray-300',
hover: 'hover:bg-gray-300',
active: 'bg-gray-300',
ring: 'ring-gray-300',
},
bg: {
white: 'bg-white text-black',
whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white',
lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white',
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
success: 'bg-emerald-600 dark:bg-pavitra-blue text-white',
danger: 'bg-red-600 text-white dark:bg-red-500 ',
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
info: " bg-blue-600 dark:bg-pavitra-blue text-white ",
contrast: {
bg: 'bg-gray-800 text-white',
border: 'border-gray-800',
hover: 'hover:bg-gray-700',
active: 'bg-gray-700',
ring: 'ring-gray-300',
},
bgHover: {
white: 'hover:bg-gray-100',
whiteDark: 'hover:bg-gray-100 hover:dark:bg-dark-800',
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100',
success:
'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue',
danger:
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
warning:
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
success: {
bg: 'bg-green-500 text-white',
border: 'border-green-500',
hover: 'hover:bg-green-600',
active: 'bg-green-600',
ring: 'ring-green-300',
},
borders: {
white: 'border-white',
whiteDark: 'border-white dark:border-dark-900',
lightDark: 'border-gray-100 dark:border-slate-800',
contrast: 'border-gray-800 dark:border-white',
success: 'border-emerald-600 dark:border-pavitra-blue',
danger: 'border-red-600 dark:border-red-500',
warning: 'border-yellow-600 dark:border-yellow-500',
info: "border-blue-600 border-blue-600 dark:border-pavitra-blue",
danger: {
bg: 'bg-red-600 text-white',
border: 'border-red-600',
hover: 'hover:bg-red-700',
active: 'bg-red-700',
ring: 'ring-red-300',
},
text: {
contrast: 'dark:text-slate-100',
success: 'text-emerald-600 dark:text-pavitra-blue',
danger: 'text-red-600 dark:text-red-500',
warning: 'text-yellow-600 dark:text-yellow-500',
info: 'text-blue-600 dark:text-pavitra-blue',
warning: {
bg: 'bg-yellow-500 text-white',
border: 'border-yellow-500',
hover: 'hover:bg-yellow-600',
active: 'bg-yellow-600',
ring: 'ring-yellow-300',
},
outlineHover: {
contrast:
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
success: 'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue',
danger:
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
warning:
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
info: {
bg: 'bg-blue-500 text-white',
border: 'border-blue-500',
hover: 'hover:bg-blue-600',
active: 'bg-blue-600',
ring: 'ring-blue-300',
},
whiteDark: { // Kept for mapping, but simplified
bg: 'bg-white text-black',
border: 'border-gray-300',
hover: 'hover:bg-gray-50',
active: 'bg-gray-100',
ring: 'ring-gray-300',
},
lightDark: { // Kept for mapping, but simplified
bg: 'bg-gray-200 text-black',
border: 'border-gray-300',
hover: 'hover:bg-gray-300',
active: 'bg-gray-300',
ring: 'ring-gray-300',
},
};
const colorSet = baseColors[color] || baseColors.info; // Default to 'info'
const base = [
'w-full',
'inline-flex',
'justify-center',
'items-center',
'whitespace-nowrap',
'focus:outline-none',
'transition-colors',
'focus:ring',
'duration-150',
'rounded',
colorSet.ring,
]
if (isOutlined) {
base.push('bg-transparent', colorSet.border, `text-${color}-500`);
if (hasHover) {
base.push(`hover:bg-${color}-500`, 'hover:text-white');
}
} else {
base.push(colorSet.bg, colorSet.border);
if (hasHover) {
base.push(colorSet.hover);
}
}
const isOutlinedProcessed = isOutlined && ['white', 'whiteDark', 'lightDark'].indexOf(color) < 0
const base = [colors.borders[color], colors.ring[color]]
if (isActive) {
base.push(colors.active[color])
} else {
base.push(isOutlinedProcessed ? colors.text[color] : colors.bg[color])
base.push(colorSet.active);
}
if (hasHover) {
base.push(isOutlinedProcessed ? colors.outlineHover[color] : colors.bgHover[color])
}
return base.join(' ')
}
}

View File

@ -19,7 +19,7 @@ export default function AsideMenu({
<>
<AsideMenuLayer
menu={props.menu}
className={`${isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0'} ${
className={`${isAsideMobileExpanded ? 'left-0' : '-left-52 lg:left-0'} ${
!isAsideLgActive ? 'lg:hidden xl:flex' : ''
}`}
onAsideLgCloseClick={props.onAsideLgClose}

View File

@ -4,8 +4,6 @@ import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks'
import Link from 'next/link';
type Props = {
menu: MenuAsideItem[]
@ -14,10 +12,6 @@ type Props = {
}
export default function AsideMenuLayer({ menu, className = '', ...props }: Props) {
const corners = useAppSelector((state) => state.style.corners);
const asideStyle = useAppSelector((state) => state.style.asideStyle)
const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle)
const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle)
const darkMode = useAppSelector((state) => state.style.darkMode)
const handleAsideLgCloseClick = (e: React.MouseEvent) => {
@ -25,35 +19,23 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
props.onAsideLgCloseClick()
}
return (
<aside
id='asideMenu'
className={`${className} zzz lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
id="asideMenu"
className={`${className} zzz lg:py-2 lg:pl-2 w-52 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
>
<div
className={`flex-1 flex flex-col overflow-hidden dark:bg-dark-900 ${asideStyle} ${corners}`}
>
<div
className={`flex flex-row h-14 items-center justify-between ${asideBrandStyle}`}
>
<div className="flex-1 flex flex-col overflow-hidden bg-gray-50 dark:bg-dark-900">
<div className="flex flex-row h-14 items-center justify-between bg-gray-100 dark:bg-dark-900">
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
<b className="font-black">Team Projects Hub</b>
<b className="font-black">TeamFlow</b>
</div>
<button
className="hidden lg:inline-block xl:hidden p-3"
onClick={handleAsideLgCloseClick}
>
<button className="hidden lg:inline-block xl:hidden p-3" onClick={handleAsideLgCloseClick}>
<BaseIcon path={mdiClose} />
</button>
</div>
<div
className={`flex-1 overflow-y-auto overflow-x-hidden ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
className={`flex-1 overflow-y-auto overflow-x-hidden ${darkMode ? 'aside-scrollbars-[slate]' : 'aside-scrollbars'
}`}
>
<AsideMenuList menu={menu} />
</div>

View File

@ -1,80 +0,0 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
attachments: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListAttachments = ({ attachments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTACHMENTS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && attachments.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/attachments/attachments-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/attachments/attachments-edit/?id=${item.id}`}
pathView={`/attachments/attachments-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && attachments.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListAttachments

View File

@ -1,68 +0,0 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_ATTACHMENTS')
return [
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/attachments/attachments-edit/?id=${params?.row?.id}`}
pathView={`/attachments/attachments-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -42,7 +42,6 @@ export default function BaseButton({
roundedFull = false,
onClick,
}: Props) {
const corners = useAppSelector((state) => state.style.corners);
const componentClass = [
'inline-flex',
'justify-center',
@ -52,19 +51,18 @@ export default function BaseButton({
'transition-colors',
'focus:ring',
'duration-150',
'border',
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
roundedFull ? 'rounded-full' : `${corners}`,
roundedFull ? 'rounded-full' : 'rounded-md',
getButtonColor(color, outline, !disabled, active),
className,
]
if (!label && icon) {
componentClass.push('p-1')
componentClass.push('p-2')
} else if (small) {
componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1')
componentClass.push('text-sm', 'px-3 py-1')
} else {
componentClass.push('py-2', roundedFull ? 'px-6' : 'px-3')
componentClass.push('py-2', 'px-4')
}
if (disabled) {

View File

@ -35,18 +35,17 @@ export default function CardBox({
onClick,
}: Props) {
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const componentClass = [
`flex dark:border-dark-700 dark:bg-dark-900`,
'flex',
'bg-white dark:bg-gray-800',
className,
corners !== 'rounded-full'? corners : 'rounded-3xl',
corners !== 'rounded-full' ? corners : 'rounded-lg',
flex,
isList ? '' : `${cardsStyle}`,
hasTable ? '' : `border-dark-700 dark:border-dark-700`,
'border border-gray-200 dark:border-gray-700',
]
if (isHoverable) {
componentClass.push('hover:shadow-lg transition-shadow duration-500')
componentClass.push('hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150')
}
return React.createElement(

View File

@ -1,99 +0,0 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
comments: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardComments = ({
comments,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COMMENTS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && comments.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/comments/comments-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.content}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/comments/comments-edit/?id=${item.id}`}
pathView={`/comments/comments-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
</dl>
</li>
))}
{!loading && comments.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardComments;

View File

@ -1,80 +0,0 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
comments: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListComments = ({ comments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COMMENTS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && comments.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/comments/comments-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/comments/comments-edit/?id=${item.id}`}
pathView={`/comments/comments-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && comments.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListComments

View File

@ -1,476 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/comments/commentsSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureCommentsCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
import ListComments from './ListComments';
const perPage = 10
const TableSampleComments = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
const router = useRouter();
const pagesList = [];
const [id, setId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [filterRequest, setFilterRequest] = React.useState('');
const [columns, setColumns] = useState<GridColDef[]>([]);
const [selectedRows, setSelectedRows] = useState([]);
const [sortModel, setSortModel] = useState([
{
field: '',
sort: 'desc',
},
]);
const { comments, loading, count, notify: commentsNotify, refetch } = useAppSelector((state) => state.comments)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const corners = useAppSelector((state) => state.style.corners);
const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
for (let i = 0; i < numPages; i++) {
pagesList.push(i);
}
const loadData = async (page = currentPage, request = filterRequest) => {
if (page !== currentPage) setCurrentPage(page);
if (request !== filterRequest) setFilterRequest(request);
const { sort, field } = sortModel[0];
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
dispatch(fetch({ limit: perPage, page, query }));
};
useEffect(() => {
if (commentsNotify.showNotification) {
notify(commentsNotify.typeNotification, commentsNotify.textNotification);
}
}, [commentsNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
loadData();
}, [sortModel, currentUser]);
useEffect(() => {
if (refetch) {
loadData(0);
dispatch(setRefetch(false));
}
}, [refetch, dispatch]);
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
const handleModalAction = () => {
setIsModalInfoActive(false)
setIsModalTrashActive(false)
}
const handleDeleteModalAction = (id: string) => {
setId(id)
setIsModalTrashActive(true)
}
const handleDeleteAction = async () => {
if (id) {
await dispatch(deleteItem(id));
await loadData(0);
setIsModalTrashActive(false);
}
};
const generateFilterRequests = useMemo(() => {
let request = '&';
filterItems.forEach((item) => {
const isRangeFilter = filters.find(
(filter) =>
filter.title === item.fields.selectedField &&
(filter.number || filter.date),
);
if (isRangeFilter) {
const from = item.fields.filterValueFrom;
const to = item.fields.filterValueTo;
if (from) {
request += `${item.fields.selectedField}Range=${from}&`;
}
if (to) {
request += `${item.fields.selectedField}Range=${to}&`;
}
} else {
const value = item.fields.filterValue;
if (value) {
request += `${item.fields.selectedField}=${value}&`;
}
}
});
return request;
}, [filterItems, filters]);
const deleteFilter = (value) => {
const newItems = filterItems.filter((item) => item.id !== value);
if (newItems.length) {
setFilterItems(newItems);
} else {
loadData(0, '');
setFilterItems(newItems);
}
};
const handleSubmit = () => {
loadData(0, generateFilterRequests);
};
const handleChange = (id) => (e) => {
const value = e.target.value;
const name = e.target.name;
setFilterItems(
filterItems.map((item) => {
if (item.id !== id) return item;
if (name === 'selectedField') return { id, fields: { [name]: value } };
return { id, fields: { ...item.fields, [name]: value } }
}),
);
};
const handleReset = () => {
setFilterItems([]);
loadData(0, '');
};
const onPageChange = (page: number) => {
loadData(page);
setCurrentPage(page);
};
useEffect(() => {
if (!currentUser) return;
loadColumns(
handleDeleteModalAction,
`comments`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
const handleTableSubmit = async (id: string, data) => {
if (!_.isEmpty(data)) {
await dispatch(update({ id, data }))
.unwrap()
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
}
};
const onDeleteRows = async (selectedRows) => {
await dispatch(deleteItemsByIds(selectedRows));
await loadData(0);
};
const controlClasses =
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
` ${bgColor} ${focusRing} ${corners} ` +
'dark:bg-slate-800 border';
const dataGrid = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={comments ?? []}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);
}}
processRowUpdate={async (newRow, oldRow) => {
const data = dataFormatter.dataGridEditFormatter(newRow);
try {
await handleTableSubmit(newRow.id, data);
return newRow;
} catch {
return oldRow;
}
}}
sortingMode={'server'}
checkboxSelection
onRowSelectionModelChange={(ids) => {
setSelectedRows(ids)
}}
onSortModelChange={(params) => {
params.length
? setSortModel(params)
: setSortModel([{ field: '', sort: 'desc' }]);
}}
rowCount={count}
pageSizeOptions={[10]}
paginationMode={'server'}
loading={loading}
onPaginationModelChange={(params) => {
onPageChange(params.page);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
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>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
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>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{comments && Array.isArray(comments) && !showGrid && (
<ListComments
comments={comments}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleComments

View File

@ -1,68 +0,0 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_COMMENTS')
return [
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/comments/comments-edit/?id=${params?.row?.id}`}
pathView={`/comments/comments-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -1,5 +1,4 @@
import React, { ReactNode } from 'react'
import LanguageSwitcher from "./LanguageSwitcher";
import { containerMaxW } from '../config'
import Logo from './Logo'
@ -26,7 +25,6 @@ export default function FooterBar({ children }: Props) {
</div>
<div className="flex item-center md:py-2 gap-4">
<LanguageSwitcher />
<a href="https://flatlogic.com/" rel="noreferrer" target="_blank">
<Logo className="w-auto h-8 md:h-6 mx-auto" />
</a>

View File

@ -38,9 +38,9 @@ const FormField = ({ icons = [], ...props }: Props) => {
`${focusRing}`,
props.hasTextareaHeight ? 'h-24' : 'h-12',
props.isBorderless ? 'border-0' : 'border',
props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? ` bg-white` : bgColor} dark:bg-dark-800`,
props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? `${bgWebsiteColor} ` : bgColor} dark:bg-dark-800`,
props.disabled ? 'bg-gray-200 text-gray-100 dark:bg-dark-900 disabled' : '',
props.borderButtom ? `border-0 border-b ${props.diversity ? "border-gray-400" : " placeholder-white border-gray-300/10 border-white "} rounded-none focus:ring-0` : '',
props.borderButtom ? `border-0 border-b ${props.diversity ? "placeholder-primaryText border-primaryText" : " placeholder-white border-white "} rounded-none focus:ring-0` : '',
].join(' ');
return (

View File

@ -77,7 +77,7 @@ const FormImagePicker = ({ label, icon, accept, color, isRoundIcon, path, schema
/>
</label>
{showFilename && !loading && (
<div className={` ${cornersRight} px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 border `}>
<div className={` ${cornersRight} px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 `}>
<span className='text-ellipsis max-w-full line-clamp-1'>
{file.name}
</span>

View File

@ -34,7 +34,7 @@ export default function ImageField({
className={`rounded-full block h-auto w-full max-w-full bg-gray-100 dark:bg-dark-900 ${imageClassName}`}
/>
) : (
<div className={'flex h-full bg-slate-100 dark:bg-dark-900/70'}>
<div className={'flex h-full dark:bg-dark-900/70'}>
<BaseIcon
className='text-black dark:text-white'
w='w-full'

View File

@ -34,7 +34,7 @@ const KanbanCard = ({
<div
ref={drag}
className={
`bg-gray-50 dark:bg-dark-800 rounded-md space-y-2 p-4 relative ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
`bg-pastelEmeraldTheme-cardColor dark:bg-dark-800 rounded-md space-y-2 p-4 relative ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
}
>
<div className={'flex items-center justify-between'}>

View File

@ -188,7 +188,7 @@ const KanbanColumn = ({
</div>
))}
{!data?.length && (
<p className={'text-center py-8 bg-gray-50 dark:bg-dark-800'}>No data</p>
<p className={'text-center py-8 bg-pastelEmeraldTheme-cardColor dark:bg-dark-800'}>No data</p>
)}
</div>
</CardBox>

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'next-i18next';
import Select, { components, SingleValueProps, OptionProps } from 'react-select';
type LanguageOption = { label: string; value: string };
@ -24,19 +23,16 @@ const SingleVal = (props: SingleValueProps<LanguageOption, false>) => (
);
const LanguageSwitcher: React.FC = () => {
const { i18n } = useTranslation();
const [mounted, setMounted] = useState(false);
const [selected, setSelected] = useState<LanguageOption>(LANGS[0]);
useEffect(() => {
setMounted(true);
setSelected(LANGS.find(l => l.value === i18n.language) ?? LANGS[0]);
}, [i18n.language]);
}, []);
const handleChange = (opt: LanguageOption | null) => {
if (!opt) return;
setSelected(opt);
i18n.changeLanguage(opt.value);
};
if (!mounted) return null;
@ -97,4 +93,4 @@ const LanguageSwitcher: React.FC = () => {
);
};
export default LanguageSwitcher;
export default LanguageSwitcher;

View File

@ -53,7 +53,7 @@ const ListActionsPopover = ({
size={'small'}
>
<BaseIcon
className={`text-black dark:text-white ${iconClassName}`}
className={`text-primaryText dark:text-white ${iconClassName}`}
w='w-10'
h='h-10'
size={24}

View File

@ -8,7 +8,7 @@ const LoadingSpinner = () => {
className='w-12 h-12 rounded-full absolute border-4 border-solid border-gray-200 dark:border-slate-800'
></div>
<div
className="w-12 h-12 rounded-full animate-spin absolute border-4 border-solid border-blue-500 dark:border-blue-500 border-t-transparent"
className="w-12 h-12 rounded-full animate-spin absolute border-4 border-solid border-pastelEmeraldTheme-iconsColor dark:border-blue-500 border-t-transparent"
></div>
</div>
</div>

View File

@ -1,11 +1,10 @@
import React, { ReactNode, useState, useEffect } from 'react'
import React, { ReactNode, useState } from 'react'
import { mdiClose, mdiDotsVertical } from '@mdi/js'
import { containerMaxW } from '../config'
import BaseIcon from './BaseIcon'
import NavBarItemPlain from './NavBarItemPlain'
import NavBarMenuList from './NavBarMenuList'
import { MenuNavBarItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks';
type Props = {
menu: MenuNavBarItem[]
@ -15,19 +14,6 @@ type Props = {
export default function NavBar({ menu, className = '', children }: Props) {
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false)
const [isScrolled, setIsScrolled] = useState(false);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
useEffect(() => {
const handleScroll = () => {
const scrolled = window.scrollY > 0;
setIsScrolled(scrolled);
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
const handleMenuNavBarToggleClick = () => {
setIsMenuNavBarActive(!isMenuNavBarActive)
@ -35,9 +21,9 @@ export default function NavBar({ menu, className = '', children }: Props) {
return (
<nav
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 transition-position w-screen lg:w-auto dark:bg-dark-800`}
className={`${className} top-0 inset-x-0 fixed bg-white h-14 z-30 transition-position w-screen border-b border-gray-200 lg:w-auto dark:bg-dark-800 dark:border-dark-700`}
>
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-pavitra-400 dark:border-dark-700`}`}>
<div className={`flex lg:items-stretch ${containerMaxW}`}>
<div className="flex flex-1 items-stretch h-14">{children}</div>
<div className="flex-none items-stretch flex h-14 lg:hidden">
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
@ -47,7 +33,7 @@ export default function NavBar({ menu, className = '', children }: Props) {
<div
className={`${
isMenuNavBarActive ? 'block' : 'hidden'
} flex items-center max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 ${bgColor} shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-dark-800`}
} flex items-center max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 bg-white lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-dark-800`}
>
<NavBarMenuList menu={menu} />
</div>

View File

@ -106,7 +106,7 @@ export default function NavBarItem({ item }: Props) {
<div
className={`${
!isDropdownActive ? 'lg:hidden' : ''
} text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`}
} text-sm border-b border-gray-100 lg:border lg:bg-pastelEmeraldTheme-cardColor lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`}
>
<ClickOutside onClickOutside={() => setIsDropdownActive(false)} excludedElements={[excludedRef]}>
<NavBarMenuList menu={item.menu} />

View File

@ -1,99 +0,0 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
notifications: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardNotifications = ({
notifications,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_NOTIFICATIONS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && notifications.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/notifications/notifications-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.message}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/notifications/notifications-edit/?id=${item.id}`}
pathView={`/notifications/notifications-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
</dl>
</li>
))}
{!loading && notifications.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardNotifications;

View File

@ -1,476 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/notifications/notificationsSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureNotificationsCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
import ListNotifications from './ListNotifications';
const perPage = 10
const TableSampleNotifications = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
const router = useRouter();
const pagesList = [];
const [id, setId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [filterRequest, setFilterRequest] = React.useState('');
const [columns, setColumns] = useState<GridColDef[]>([]);
const [selectedRows, setSelectedRows] = useState([]);
const [sortModel, setSortModel] = useState([
{
field: '',
sort: 'desc',
},
]);
const { notifications, loading, count, notify: notificationsNotify, refetch } = useAppSelector((state) => state.notifications)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const corners = useAppSelector((state) => state.style.corners);
const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
for (let i = 0; i < numPages; i++) {
pagesList.push(i);
}
const loadData = async (page = currentPage, request = filterRequest) => {
if (page !== currentPage) setCurrentPage(page);
if (request !== filterRequest) setFilterRequest(request);
const { sort, field } = sortModel[0];
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
dispatch(fetch({ limit: perPage, page, query }));
};
useEffect(() => {
if (notificationsNotify.showNotification) {
notify(notificationsNotify.typeNotification, notificationsNotify.textNotification);
}
}, [notificationsNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
loadData();
}, [sortModel, currentUser]);
useEffect(() => {
if (refetch) {
loadData(0);
dispatch(setRefetch(false));
}
}, [refetch, dispatch]);
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
const handleModalAction = () => {
setIsModalInfoActive(false)
setIsModalTrashActive(false)
}
const handleDeleteModalAction = (id: string) => {
setId(id)
setIsModalTrashActive(true)
}
const handleDeleteAction = async () => {
if (id) {
await dispatch(deleteItem(id));
await loadData(0);
setIsModalTrashActive(false);
}
};
const generateFilterRequests = useMemo(() => {
let request = '&';
filterItems.forEach((item) => {
const isRangeFilter = filters.find(
(filter) =>
filter.title === item.fields.selectedField &&
(filter.number || filter.date),
);
if (isRangeFilter) {
const from = item.fields.filterValueFrom;
const to = item.fields.filterValueTo;
if (from) {
request += `${item.fields.selectedField}Range=${from}&`;
}
if (to) {
request += `${item.fields.selectedField}Range=${to}&`;
}
} else {
const value = item.fields.filterValue;
if (value) {
request += `${item.fields.selectedField}=${value}&`;
}
}
});
return request;
}, [filterItems, filters]);
const deleteFilter = (value) => {
const newItems = filterItems.filter((item) => item.id !== value);
if (newItems.length) {
setFilterItems(newItems);
} else {
loadData(0, '');
setFilterItems(newItems);
}
};
const handleSubmit = () => {
loadData(0, generateFilterRequests);
};
const handleChange = (id) => (e) => {
const value = e.target.value;
const name = e.target.name;
setFilterItems(
filterItems.map((item) => {
if (item.id !== id) return item;
if (name === 'selectedField') return { id, fields: { [name]: value } };
return { id, fields: { ...item.fields, [name]: value } }
}),
);
};
const handleReset = () => {
setFilterItems([]);
loadData(0, '');
};
const onPageChange = (page: number) => {
loadData(page);
setCurrentPage(page);
};
useEffect(() => {
if (!currentUser) return;
loadColumns(
handleDeleteModalAction,
`notifications`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
const handleTableSubmit = async (id: string, data) => {
if (!_.isEmpty(data)) {
await dispatch(update({ id, data }))
.unwrap()
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
}
};
const onDeleteRows = async (selectedRows) => {
await dispatch(deleteItemsByIds(selectedRows));
await loadData(0);
};
const controlClasses =
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
` ${bgColor} ${focusRing} ${corners} ` +
'dark:bg-slate-800 border';
const dataGrid = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={notifications ?? []}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);
}}
processRowUpdate={async (newRow, oldRow) => {
const data = dataFormatter.dataGridEditFormatter(newRow);
try {
await handleTableSubmit(newRow.id, data);
return newRow;
} catch {
return oldRow;
}
}}
sortingMode={'server'}
checkboxSelection
onRowSelectionModelChange={(ids) => {
setSelectedRows(ids)
}}
onSortModelChange={(params) => {
params.length
? setSortModel(params)
: setSortModel([{ field: '', sort: 'desc' }]);
}}
rowCount={count}
pageSizeOptions={[10]}
paginationMode={'server'}
loading={loading}
onPaginationModelChange={(params) => {
onPageChange(params.page);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
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>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
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>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{notifications && Array.isArray(notifications) && !showGrid && (
<ListNotifications
notifications={notifications}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleNotifications

View File

@ -1,68 +0,0 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_NOTIFICATIONS')
return [
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/notifications/notifications-edit/?id=${params?.row?.id}`}
pathView={`/notifications/notifications-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -37,7 +37,7 @@ const ListPermissions = ({ permissions, loading, onDelete, currentPage, numPages
{!loading && permissions.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/permissions/permissions-view/?id=${item.id}`}

View File

@ -412,13 +412,13 @@ const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
type='submit' color='info'
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
type='reset' color='info' outline
label='Cancel'
onClick={handleReset}
/>

View File

@ -37,7 +37,7 @@ const ListProjects = ({ projects, loading, onDelete, currentPage, numPages, onPa
{!loading && projects.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/projects/projects-view/?id=${item.id}`}

View File

@ -421,13 +421,13 @@ const TableSampleProjects = ({ filterItems, setFilterItems, filters, showGrid })
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
type='submit' color='info'
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
type='reset' color='info' outline
label='Cancel'
onClick={handleReset}
/>

View File

@ -1,99 +0,0 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
reports: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardReports = ({
reports,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REPORTS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && reports.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/reports/reports-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.title}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/reports/reports-edit/?id=${item.id}`}
pathView={`/reports/reports-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
</dl>
</li>
))}
{!loading && reports.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardReports;

View File

@ -1,80 +0,0 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
reports: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListReports = ({ reports, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REPORTS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && reports.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/reports/reports-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/reports/reports-edit/?id=${item.id}`}
pathView={`/reports/reports-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && reports.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListReports

View File

@ -1,463 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/reports/reportsSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureReportsCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
const perPage = 10
const TableSampleReports = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
const router = useRouter();
const pagesList = [];
const [id, setId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [filterRequest, setFilterRequest] = React.useState('');
const [columns, setColumns] = useState<GridColDef[]>([]);
const [selectedRows, setSelectedRows] = useState([]);
const [sortModel, setSortModel] = useState([
{
field: '',
sort: 'desc',
},
]);
const { reports, loading, count, notify: reportsNotify, refetch } = useAppSelector((state) => state.reports)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const corners = useAppSelector((state) => state.style.corners);
const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
for (let i = 0; i < numPages; i++) {
pagesList.push(i);
}
const loadData = async (page = currentPage, request = filterRequest) => {
if (page !== currentPage) setCurrentPage(page);
if (request !== filterRequest) setFilterRequest(request);
const { sort, field } = sortModel[0];
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
dispatch(fetch({ limit: perPage, page, query }));
};
useEffect(() => {
if (reportsNotify.showNotification) {
notify(reportsNotify.typeNotification, reportsNotify.textNotification);
}
}, [reportsNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
loadData();
}, [sortModel, currentUser]);
useEffect(() => {
if (refetch) {
loadData(0);
dispatch(setRefetch(false));
}
}, [refetch, dispatch]);
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
const handleModalAction = () => {
setIsModalInfoActive(false)
setIsModalTrashActive(false)
}
const handleDeleteModalAction = (id: string) => {
setId(id)
setIsModalTrashActive(true)
}
const handleDeleteAction = async () => {
if (id) {
await dispatch(deleteItem(id));
await loadData(0);
setIsModalTrashActive(false);
}
};
const generateFilterRequests = useMemo(() => {
let request = '&';
filterItems.forEach((item) => {
const isRangeFilter = filters.find(
(filter) =>
filter.title === item.fields.selectedField &&
(filter.number || filter.date),
);
if (isRangeFilter) {
const from = item.fields.filterValueFrom;
const to = item.fields.filterValueTo;
if (from) {
request += `${item.fields.selectedField}Range=${from}&`;
}
if (to) {
request += `${item.fields.selectedField}Range=${to}&`;
}
} else {
const value = item.fields.filterValue;
if (value) {
request += `${item.fields.selectedField}=${value}&`;
}
}
});
return request;
}, [filterItems, filters]);
const deleteFilter = (value) => {
const newItems = filterItems.filter((item) => item.id !== value);
if (newItems.length) {
setFilterItems(newItems);
} else {
loadData(0, '');
setFilterItems(newItems);
}
};
const handleSubmit = () => {
loadData(0, generateFilterRequests);
};
const handleChange = (id) => (e) => {
const value = e.target.value;
const name = e.target.name;
setFilterItems(
filterItems.map((item) => {
if (item.id !== id) return item;
if (name === 'selectedField') return { id, fields: { [name]: value } };
return { id, fields: { ...item.fields, [name]: value } }
}),
);
};
const handleReset = () => {
setFilterItems([]);
loadData(0, '');
};
const onPageChange = (page: number) => {
loadData(page);
setCurrentPage(page);
};
useEffect(() => {
if (!currentUser) return;
loadColumns(
handleDeleteModalAction,
`reports`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
const handleTableSubmit = async (id: string, data) => {
if (!_.isEmpty(data)) {
await dispatch(update({ id, data }))
.unwrap()
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
}
};
const onDeleteRows = async (selectedRows) => {
await dispatch(deleteItemsByIds(selectedRows));
await loadData(0);
};
const controlClasses =
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
` ${bgColor} ${focusRing} ${corners} ` +
'dark:bg-slate-800 border';
const dataGrid = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={reports ?? []}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);
}}
processRowUpdate={async (newRow, oldRow) => {
const data = dataFormatter.dataGridEditFormatter(newRow);
try {
await handleTableSubmit(newRow.id, data);
return newRow;
} catch {
return oldRow;
}
}}
sortingMode={'server'}
checkboxSelection
onRowSelectionModelChange={(ids) => {
setSelectedRows(ids)
}}
onSortModelChange={(params) => {
params.length
? setSortModel(params)
: setSortModel([{ field: '', sort: 'desc' }]);
}}
rowCount={count}
pageSizeOptions={[10]}
paginationMode={'server'}
loading={loading}
onPaginationModelChange={(params) => {
onPageChange(params.page);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
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>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
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>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleReports

View File

@ -1,68 +0,0 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_REPORTS')
return [
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/reports/reports-edit/?id=${params?.row?.id}`}
pathView={`/reports/reports-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -37,7 +37,7 @@ const ListRoles = ({ roles, loading, onDelete, currentPage, numPages, onPageChan
{!loading && roles.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/roles/roles-view/?id=${item.id}`}

View File

@ -412,13 +412,13 @@ const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) =>
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
type='submit' color='info'
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
type='reset' color='info' outline
label='Cancel'
onClick={handleReset}
/>

View File

@ -2,10 +2,8 @@ import React from 'react';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useAppSelector } from '../stores/hooks';
import { useTranslation } from 'next-i18next';
const Search = () => {
const { t } = useTranslation('common');
const router = useRouter();
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const corners = useAppSelector((state) => state.style.corners);
@ -13,9 +11,9 @@ const Search = () => {
const validateSearch = (value) => {
let error;
if (!value) {
error = t('components.search.required');
error = 'Required';
} else if (value.length < 2) {
error = t('components.search.minLength', { count: 2 });
error = 'Minimum length: 2 characters';
}
return error;
};
@ -38,7 +36,7 @@ const Search = () => {
id='search'
name='search'
validate={validateSearch}
placeholder={t('components.search.placeholder', { defaultValue: '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 ? (
@ -49,4 +47,4 @@ const Search = () => {
</Formik>
);
};
export default Search;
export default Search;

View File

@ -6,5 +6,5 @@ type Props = {
}
export default function SectionMain({ children }: Props) {
return <section className={`p-6 ${containerMaxW}`}>{children}</section>
return <section className={`p-8 ${containerMaxW}`}>{children}</section>
}

View File

@ -17,9 +17,9 @@ const SectionTitle = ({ custom = false, first = false, last = false, children }:
}
return (
<section className={`py-24 px-6 lg:px-0 lg:max-w-2xl lg:mx-auto text-center ${classAddon}`}>
<section className={`py-12 px-6 lg:px-0 lg:max-w-2xl lg:mx-auto text-center ${classAddon}`}>
{custom && children}
{!custom && <h1 className="text-2xl text-gray-500 dark:text-slate-400">{children}</h1>}
{!custom && <h1 className="text-2xl text-gray-700 dark:text-gray-300">{children}</h1>}
</section>
)
}

View File

@ -1,8 +1,5 @@
import { mdiCog } from '@mdi/js'
import React, { Children, ReactNode } from 'react'
import BaseButton from './BaseButton'
import React, { ReactNode } from 'react'
import BaseIcon from './BaseIcon'
import IconRounded from './IconRounded'
import { humanize } from '../helpers/humanize';
type Props = {
@ -13,17 +10,13 @@ type Props = {
}
export default function SectionTitleLineWithButton({ icon, title, main = false, children }: Props) {
const hasChildren = !!Children.count(children)
return (
<section className={`${main ? '' : 'pt-6'} mb-6 flex items-center justify-between`}>
<div className="flex items-center justify-start">
{icon && main && <IconRounded icon={icon} color="light" className="mr-3" bg />}
{icon && !main && <BaseIcon path={icon} className="mr-2" size="20" />}
<h1 className={`leading-tight ${main ? 'text-3xl' : 'text-2xl'}`}>{humanize(title)}</h1>
{icon && <BaseIcon path={icon} className="mr-3" size="24" />}
<h1 className={`leading-tight ${main ? 'text-2xl' : 'text-xl'}`}>{humanize(title)}</h1>
</div>
{children}
{!hasChildren && <BaseButton icon={mdiCog} color="whiteDark" />}
</section>
)
}

View File

@ -1,6 +1,10 @@
import React, { useEffect, useId, useState } from 'react';
import Switch from "react-switch";
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from '../../tailwind.config.js';
const fullConfig = resolveConfig(tailwindConfig as any);
export const SwitchField = ({
@ -16,7 +20,10 @@ export const SwitchField = ({
};
const buttonColor = fullConfig.theme.accentColor.pastelEmeraldTheme?.buttonColor;
const cardColor = fullConfig.theme.accentColor.pastelEmeraldTheme?.cardColor;
return (
<Switch checkedIcon={false} uncheckedIcon={false} className={'check'} onChange={handleChange} checked={!!field?.value} disabled={disabled} />
<Switch checkedIcon={false} uncheckedIcon={false} className={'check'} onChange={handleChange} onColor={buttonColor} offColor={cardColor} checked={!!field?.value} disabled={disabled} />
);
};

View File

@ -37,7 +37,7 @@ const ListTasks = ({ tasks, loading, onDelete, currentPage, numPages, onPageChan
{!loading && tasks.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/tasks/tasks-view/?id=${item.id}`}

View File

@ -98,18 +98,16 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
setKanbanColumns([
{ id: "Backlog", label: "Backlog" },
{ id: "ToDo", label: "ToDo" },
{ id: "InProgress", label: "InProgress" },
{ id: "InReview", label: "InReview" },
{ id: "Done", label: "Done" },
{ id: "Blocked", label: "Blocked" },
{ id: "Done", label: "Done" },
]);
@ -447,13 +445,13 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
type='submit' color='info'
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
type='reset' color='info' outline
label='Cancel'
onClick={handleReset}
/>

View File

@ -1,99 +0,0 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
team_members: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardTeam_members = ({
team_members,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_TEAM_MEMBERS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && team_members.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/team_members/team_members-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.display_label}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/team_members/team_members-edit/?id=${item.id}`}
pathView={`/team_members/team_members-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
</dl>
</li>
))}
{!loading && team_members.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardTeam_members;

View File

@ -1,80 +0,0 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
team_members: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListTeam_members = ({ team_members, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_TEAM_MEMBERS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && team_members.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/team_members/team_members-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/team_members/team_members-edit/?id=${item.id}`}
pathView={`/team_members/team_members-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && team_members.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListTeam_members

View File

@ -1,463 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/team_members/team_membersSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureTeam_membersCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
const perPage = 10
const TableSampleTeam_members = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
const router = useRouter();
const pagesList = [];
const [id, setId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [filterRequest, setFilterRequest] = React.useState('');
const [columns, setColumns] = useState<GridColDef[]>([]);
const [selectedRows, setSelectedRows] = useState([]);
const [sortModel, setSortModel] = useState([
{
field: '',
sort: 'desc',
},
]);
const { team_members, loading, count, notify: team_membersNotify, refetch } = useAppSelector((state) => state.team_members)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const corners = useAppSelector((state) => state.style.corners);
const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
for (let i = 0; i < numPages; i++) {
pagesList.push(i);
}
const loadData = async (page = currentPage, request = filterRequest) => {
if (page !== currentPage) setCurrentPage(page);
if (request !== filterRequest) setFilterRequest(request);
const { sort, field } = sortModel[0];
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
dispatch(fetch({ limit: perPage, page, query }));
};
useEffect(() => {
if (team_membersNotify.showNotification) {
notify(team_membersNotify.typeNotification, team_membersNotify.textNotification);
}
}, [team_membersNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
loadData();
}, [sortModel, currentUser]);
useEffect(() => {
if (refetch) {
loadData(0);
dispatch(setRefetch(false));
}
}, [refetch, dispatch]);
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
const handleModalAction = () => {
setIsModalInfoActive(false)
setIsModalTrashActive(false)
}
const handleDeleteModalAction = (id: string) => {
setId(id)
setIsModalTrashActive(true)
}
const handleDeleteAction = async () => {
if (id) {
await dispatch(deleteItem(id));
await loadData(0);
setIsModalTrashActive(false);
}
};
const generateFilterRequests = useMemo(() => {
let request = '&';
filterItems.forEach((item) => {
const isRangeFilter = filters.find(
(filter) =>
filter.title === item.fields.selectedField &&
(filter.number || filter.date),
);
if (isRangeFilter) {
const from = item.fields.filterValueFrom;
const to = item.fields.filterValueTo;
if (from) {
request += `${item.fields.selectedField}Range=${from}&`;
}
if (to) {
request += `${item.fields.selectedField}Range=${to}&`;
}
} else {
const value = item.fields.filterValue;
if (value) {
request += `${item.fields.selectedField}=${value}&`;
}
}
});
return request;
}, [filterItems, filters]);
const deleteFilter = (value) => {
const newItems = filterItems.filter((item) => item.id !== value);
if (newItems.length) {
setFilterItems(newItems);
} else {
loadData(0, '');
setFilterItems(newItems);
}
};
const handleSubmit = () => {
loadData(0, generateFilterRequests);
};
const handleChange = (id) => (e) => {
const value = e.target.value;
const name = e.target.name;
setFilterItems(
filterItems.map((item) => {
if (item.id !== id) return item;
if (name === 'selectedField') return { id, fields: { [name]: value } };
return { id, fields: { ...item.fields, [name]: value } }
}),
);
};
const handleReset = () => {
setFilterItems([]);
loadData(0, '');
};
const onPageChange = (page: number) => {
loadData(page);
setCurrentPage(page);
};
useEffect(() => {
if (!currentUser) return;
loadColumns(
handleDeleteModalAction,
`team_members`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
const handleTableSubmit = async (id: string, data) => {
if (!_.isEmpty(data)) {
await dispatch(update({ id, data }))
.unwrap()
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
}
};
const onDeleteRows = async (selectedRows) => {
await dispatch(deleteItemsByIds(selectedRows));
await loadData(0);
};
const controlClasses =
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
` ${bgColor} ${focusRing} ${corners} ` +
'dark:bg-slate-800 border';
const dataGrid = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={team_members ?? []}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);
}}
processRowUpdate={async (newRow, oldRow) => {
const data = dataFormatter.dataGridEditFormatter(newRow);
try {
await handleTableSubmit(newRow.id, data);
return newRow;
} catch {
return oldRow;
}
}}
sortingMode={'server'}
checkboxSelection
onRowSelectionModelChange={(ids) => {
setSelectedRows(ids)
}}
onSortModelChange={(params) => {
params.length
? setSortModel(params)
: setSortModel([{ field: '', sort: 'desc' }]);
}}
rowCount={count}
pageSizeOptions={[10]}
paginationMode={'server'}
loading={loading}
onPaginationModelChange={(params) => {
onPageChange(params.page);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
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>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
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>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.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>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
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>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleTeam_members

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