Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
8
502.html
8
502.html
@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Login</title>
|
<title>Service Starting</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -129,8 +129,8 @@
|
|||||||
<p class="tip">The application is currently launching. The page will automatically refresh once site is
|
<p class="tip">The application is currently launching. The page will automatically refresh once site is
|
||||||
available.</p>
|
available.</p>
|
||||||
<div class="project-info">
|
<div class="project-info">
|
||||||
<h2>Web page for saving and administrating time (UTTN RA2)</h2>
|
<h2>Gestor de Tareas Personales</h2>
|
||||||
<p>In this web page we're gonna see a small web page with login, Dashboard & CRUD </p>
|
<p>Aplicación web para gestionar tareas personales con registro/login y CRUD con filtros por estado y prioridad.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="loader-container">
|
<div class="loader-container">
|
||||||
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"
|
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"
|
||||||
@ -138,7 +138,7 @@
|
|||||||
<div class="loader"></div>
|
<div class="loader"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<video width="20%" height="67" controls loop>
|
<video width="100%" height="315" controls loop>
|
||||||
<source
|
<source
|
||||||
src="https://flatlogic.com/blog/wp-content/uploads/2025/04/20250430_1336_professional_dynamo_spinner_simple_compose_01jt349yvtenxt7xhg8hhr85j8.mp4"
|
src="https://flatlogic.com/blog/wp-content/uploads/2025/04/20250430_1336_professional_dynamo_spinner_simple_compose_01jt349yvtenxt7xhg8hhr85j8.mp4"
|
||||||
type="video/mp4">
|
type="video/mp4">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
const FileDBApi = require('./file');
|
const FileDBApi = require('./file');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
@ -32,7 +33,7 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
status: data.status
|
status: data.status
|
||||||
||
|
||
|
||||||
'Pending'
|
null
|
||||||
,
|
,
|
||||||
|
|
||||||
due_date: data.due_date
|
due_date: data.due_date
|
||||||
@ -42,17 +43,26 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
priority: data.priority
|
priority: data.priority
|
||||||
||
|
||
|
||||||
'Low'
|
null
|
||||||
,
|
,
|
||||||
|
|
||||||
importHash: data.importHash || null,
|
importHash: data.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
updatedById: currentUser.id,
|
updatedById: currentUser.id,
|
||||||
userId: data.user || currentUser.id, // Auto-assign to current user if not provided
|
|
||||||
},
|
},
|
||||||
{ transaction },
|
{ transaction },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
await tasks.setUser( data.user || null, {
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +87,7 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
status: item.status
|
status: item.status
|
||||||
||
|
||
|
||||||
'Pending'
|
null
|
||||||
,
|
,
|
||||||
|
|
||||||
due_date: item.due_date
|
due_date: item.due_date
|
||||||
@ -87,19 +97,21 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
priority: item.priority
|
priority: item.priority
|
||||||
||
|
||
|
||||||
'Low'
|
null
|
||||||
,
|
,
|
||||||
|
|
||||||
importHash: item.importHash || null,
|
importHash: item.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
updatedById: currentUser.id,
|
updatedById: currentUser.id,
|
||||||
userId: currentUser.id, // Bulk import also assigns to importer
|
|
||||||
createdAt: new Date(Date.now() + index * 1000),
|
createdAt: new Date(Date.now() + index * 1000),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Bulk create items
|
// Bulk create items
|
||||||
const tasks = await db.tasks.bulkCreate(tasksData, { transaction });
|
const tasks = await db.tasks.bulkCreate(tasksData, { transaction });
|
||||||
|
|
||||||
|
// For each item created, replace relation files
|
||||||
|
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,22 +122,47 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
const tasks = await db.tasks.findByPk(id, {}, {transaction});
|
const tasks = await db.tasks.findByPk(id, {}, {transaction});
|
||||||
|
|
||||||
if (!tasks) {
|
|
||||||
throw new Error('Task not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePayload = {};
|
const updatePayload = {};
|
||||||
|
|
||||||
if (data.title !== undefined) updatePayload.title = data.title;
|
if (data.title !== undefined) updatePayload.title = data.title;
|
||||||
|
|
||||||
|
|
||||||
if (data.description !== undefined) updatePayload.description = data.description;
|
if (data.description !== undefined) updatePayload.description = data.description;
|
||||||
|
|
||||||
|
|
||||||
if (data.status !== undefined) updatePayload.status = data.status;
|
if (data.status !== undefined) updatePayload.status = data.status;
|
||||||
|
|
||||||
|
|
||||||
if (data.due_date !== undefined) updatePayload.due_date = data.due_date;
|
if (data.due_date !== undefined) updatePayload.due_date = data.due_date;
|
||||||
|
|
||||||
|
|
||||||
if (data.priority !== undefined) updatePayload.priority = data.priority;
|
if (data.priority !== undefined) updatePayload.priority = data.priority;
|
||||||
|
|
||||||
|
|
||||||
updatePayload.updatedById = currentUser.id;
|
updatePayload.updatedById = currentUser.id;
|
||||||
|
|
||||||
await tasks.update(updatePayload, {transaction});
|
await tasks.update(updatePayload, {transaction});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (data.user !== undefined) {
|
||||||
|
await tasks.setUser(
|
||||||
|
|
||||||
|
data.user,
|
||||||
|
|
||||||
|
{ transaction }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +175,6 @@ module.exports = class TasksDBApi {
|
|||||||
id: {
|
id: {
|
||||||
[Op.in]: ids,
|
[Op.in]: ids,
|
||||||
},
|
},
|
||||||
userId: currentUser.id, // Only allow deleting own tasks
|
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
@ -163,14 +199,7 @@ module.exports = class TasksDBApi {
|
|||||||
const currentUser = (options && options.currentUser) || {id: null};
|
const currentUser = (options && options.currentUser) || {id: null};
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const transaction = (options && options.transaction) || undefined;
|
||||||
|
|
||||||
const tasks = await db.tasks.findOne({
|
const tasks = await db.tasks.findByPk(id, options);
|
||||||
where: { id, userId: currentUser.id },
|
|
||||||
transaction
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tasks) {
|
|
||||||
throw new Error('Task not found or permission denied');
|
|
||||||
}
|
|
||||||
|
|
||||||
await tasks.update({
|
await tasks.update({
|
||||||
deletedBy: currentUser.id
|
deletedBy: currentUser.id
|
||||||
@ -199,6 +228,18 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
const output = tasks.get({plain: true});
|
const output = tasks.get({plain: true});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
output.user = await tasks.getUser({
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,10 +252,9 @@ module.exports = class TasksDBApi {
|
|||||||
let where = {};
|
let where = {};
|
||||||
const currentPage = +filter.page;
|
const currentPage = +filter.page;
|
||||||
|
|
||||||
const currentUser = (options && options.currentUser) || { id: null };
|
|
||||||
|
|
||||||
// RESTRICT TO OWN TASKS
|
|
||||||
where.userId = currentUser.id;
|
|
||||||
|
|
||||||
offset = currentPage * limit;
|
offset = currentPage * limit;
|
||||||
|
|
||||||
@ -222,7 +262,28 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
const transaction = (options && options.transaction) || undefined;
|
const transaction = (options && options.transaction) || undefined;
|
||||||
|
|
||||||
let include = []; // Removed User include for privacy/simplicity in personal manager
|
let include = [
|
||||||
|
|
||||||
|
{
|
||||||
|
model: db.users,
|
||||||
|
as: 'user',
|
||||||
|
|
||||||
|
where: filter.user ? {
|
||||||
|
[Op.or]: [
|
||||||
|
{ id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } },
|
||||||
|
{
|
||||||
|
firstName: {
|
||||||
|
[Op.or]: filter.user.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} : {},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
if (filter.id) {
|
if (filter.id) {
|
||||||
@ -256,6 +317,10 @@ module.exports = class TasksDBApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (filter.due_dateRange) {
|
if (filter.due_dateRange) {
|
||||||
const [start, end] = filter.due_dateRange;
|
const [start, end] = filter.due_dateRange;
|
||||||
|
|
||||||
@ -304,6 +369,11 @@ module.exports = class TasksDBApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (filter.createdAtRange) {
|
if (filter.createdAtRange) {
|
||||||
const [start, end] = filter.createdAtRange;
|
const [start, end] = filter.createdAtRange;
|
||||||
|
|
||||||
@ -330,6 +400,8 @@ module.exports = class TasksDBApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
where,
|
where,
|
||||||
include,
|
include,
|
||||||
@ -338,6 +410,7 @@ module.exports = class TasksDBApi {
|
|||||||
? [[filter.field, filter.sort]]
|
? [[filter.field, filter.sort]]
|
||||||
: [['createdAt', 'desc']],
|
: [['createdAt', 'desc']],
|
||||||
transaction: options?.transaction,
|
transaction: options?.transaction,
|
||||||
|
logging: console.log
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!options?.countOnly) {
|
if (!options?.countOnly) {
|
||||||
@ -361,6 +434,8 @@ module.exports = class TasksDBApi {
|
|||||||
static async findAllAutocomplete(query, limit, offset, ) {
|
static async findAllAutocomplete(query, limit, offset, ) {
|
||||||
let where = {};
|
let where = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
where = {
|
where = {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
@ -390,3 +465,4 @@ module.exports = class TasksDBApi {
|
|||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
module.exports = {
|
|
||||||
up: async (queryInterface, Sequelize) => {
|
|
||||||
// 1. Change InProgress to In Progress in the data
|
|
||||||
await queryInterface.sequelize.query(
|
|
||||||
"UPDATE tasks SET status = 'Pending' WHERE status = 'InProgress' OR status IS NULL"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. We need to handle the ENUM change. PostgreSQL ENUMs are tricky to update.
|
|
||||||
// The safest way is to rename the old enum, create a new one, and drop the old one.
|
|
||||||
// However, for this environment, we might just try to add the value if it's missing,
|
|
||||||
// or use a simpler approach if the DB allows.
|
|
||||||
|
|
||||||
// Check current type and update it.
|
|
||||||
try {
|
|
||||||
await queryInterface.sequelize.query(`ALTER TYPE "enum_tasks_status" ADD VALUE IF NOT EXISTS 'In Progress'`);
|
|
||||||
await queryInterface.sequelize.query(`UPDATE tasks SET status = 'In Progress' WHERE status = 'InProgress'`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error updating enum type:', e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (queryInterface, Sequelize) => {
|
|
||||||
// Revert if needed
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -16,35 +16,61 @@ module.exports = function(sequelize, DataTypes) {
|
|||||||
|
|
||||||
title: {
|
title: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
allowNull: false, // User requested Title to be obligatory
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
description: {
|
description: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
status: {
|
status: {
|
||||||
type: DataTypes.ENUM,
|
type: DataTypes.ENUM,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
values: [
|
values: [
|
||||||
|
|
||||||
"Pending",
|
"Pending",
|
||||||
|
|
||||||
|
|
||||||
"InProgress",
|
"InProgress",
|
||||||
|
|
||||||
|
|
||||||
"Done"
|
"Done"
|
||||||
|
|
||||||
],
|
],
|
||||||
defaultValue: "Pending",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
due_date: {
|
due_date: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
priority: {
|
priority: {
|
||||||
type: DataTypes.ENUM,
|
type: DataTypes.ENUM,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
values: [
|
values: [
|
||||||
|
|
||||||
"Low",
|
"Low",
|
||||||
|
|
||||||
|
|
||||||
"Medium",
|
"Medium",
|
||||||
|
|
||||||
|
|
||||||
"High"
|
"High"
|
||||||
|
|
||||||
],
|
],
|
||||||
defaultValue: "Low",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
importHash: {
|
importHash: {
|
||||||
@ -61,6 +87,20 @@ priority: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
tasks.associate = (db) => {
|
tasks.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.tasks.belongsTo(db.users, {
|
db.tasks.belongsTo(db.users, {
|
||||||
as: 'user',
|
as: 'user',
|
||||||
foreignKey: {
|
foreignKey: {
|
||||||
@ -69,6 +109,9 @@ priority: {
|
|||||||
constraints: false,
|
constraints: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
db.tasks.belongsTo(db.users, {
|
db.tasks.belongsTo(db.users, {
|
||||||
as: 'createdBy',
|
as: 'createdBy',
|
||||||
});
|
});
|
||||||
@ -78,5 +121,9 @@ priority: {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {dataGridStyles} from "../../styles";
|
|||||||
|
|
||||||
|
|
||||||
import KanbanBoard from '../KanbanBoard/KanbanBoard';
|
import KanbanBoard from '../KanbanBoard/KanbanBoard';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
|
||||||
const perPage = 10
|
const perPage = 10
|
||||||
@ -29,6 +30,7 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const pagesList = [];
|
||||||
const [id, setId] = useState(null);
|
const [id, setId] = useState(null);
|
||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [filterRequest, setFilterRequest] = React.useState('');
|
const [filterRequest, setFilterRequest] = React.useState('');
|
||||||
@ -49,6 +51,10 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
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) => {
|
const loadData = async (page = currentPage, request = filterRequest) => {
|
||||||
if (page !== currentPage) setCurrentPage(page);
|
if (page !== currentPage) setCurrentPage(page);
|
||||||
@ -77,22 +83,35 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
}
|
}
|
||||||
}, [refetch, dispatch]);
|
}, [refetch, dispatch]);
|
||||||
|
|
||||||
|
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
|
||||||
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
|
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
|
||||||
|
|
||||||
const handleModalAction = () => {
|
const handleModalAction = () => {
|
||||||
|
setIsModalInfoActive(false)
|
||||||
setIsModalTrashActive(false)
|
setIsModalTrashActive(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setKanbanColumns([
|
setKanbanColumns([
|
||||||
|
|
||||||
{ id: "Pending", label: "Pending" },
|
{ id: "Pending", label: "Pending" },
|
||||||
|
|
||||||
{ id: "InProgress", label: "InProgress" },
|
{ id: "InProgress", label: "InProgress" },
|
||||||
|
|
||||||
{ id: "Done", label: "Done" },
|
{ id: "Done", label: "Done" },
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleDeleteModalAction = (id: string) => {
|
const handleDeleteModalAction = (id: string) => {
|
||||||
setId(id)
|
setId(id)
|
||||||
setIsModalTrashActive(true)
|
setIsModalTrashActive(true)
|
||||||
@ -140,14 +159,18 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
setFilterItems(newItems);
|
setFilterItems(newItems);
|
||||||
} else {
|
} else {
|
||||||
loadData(0, '');
|
loadData(0, '');
|
||||||
|
|
||||||
setKanbanFilters('');
|
setKanbanFilters('');
|
||||||
|
|
||||||
setFilterItems(newItems);
|
setFilterItems(newItems);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
loadData(0, generateFilterRequests);
|
loadData(0, generateFilterRequests);
|
||||||
|
|
||||||
setKanbanFilters(generateFilterRequests);
|
setKanbanFilters(generateFilterRequests);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (id) => (e) => {
|
const handleChange = (id) => (e) => {
|
||||||
@ -167,7 +190,9 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
|
|||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setFilterItems([]);
|
setFilterItems([]);
|
||||||
loadData(0, '');
|
loadData(0, '');
|
||||||
|
|
||||||
setKanbanFilters('');
|
setKanbanFilters('');
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPageChange = (page: number) => {
|
const onPageChange = (page: number) => {
|
||||||
|
|||||||
@ -1,94 +1,154 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import BaseIcon from '../BaseIcon';
|
||||||
|
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||||
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
GridActionsCellItem,
|
GridActionsCellItem,
|
||||||
GridRowParams,
|
GridRowParams,
|
||||||
|
GridValueGetterParams,
|
||||||
} from '@mui/x-data-grid';
|
} 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 ListActionsPopover from '../ListActionsPopover';
|
||||||
|
|
||||||
import {hasPermission} from "../../helpers/userPermissions";
|
import {hasPermission} from "../../helpers/userPermissions";
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
type Params = (id: string) => void;
|
type Params = (id: string) => void;
|
||||||
|
|
||||||
export const loadColumns = async (
|
export const loadColumns = async (
|
||||||
onDelete: Params,
|
onDelete: Params,
|
||||||
entityName: string,
|
entityName: string,
|
||||||
|
|
||||||
user
|
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_TASKS')
|
const hasUpdatePermission = hasPermission(user, 'UPDATE_TASKS')
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'user',
|
||||||
|
headerName: 'User',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
|
||||||
|
sortable: false,
|
||||||
|
type: 'singleSelect',
|
||||||
|
getOptionValue: (value: any) => value?.id,
|
||||||
|
getOptionLabel: (value: any) => value?.label,
|
||||||
|
valueOptions: await callOptionsApi('users'),
|
||||||
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
|
params?.value?.id ?? params?.value,
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'title',
|
field: 'title',
|
||||||
headerName: 'Title',
|
headerName: 'Title',
|
||||||
flex: 2,
|
flex: 1,
|
||||||
minWidth: 200,
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
headerName: 'Description',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
headerName: 'Status',
|
headerName: 'Status',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
renderCell: (params) => {
|
|
||||||
const colors = {
|
|
||||||
'Pending': 'text-gray-500 bg-gray-100',
|
|
||||||
'In Progress': 'text-blue-500 bg-blue-100',
|
|
||||||
'Done': 'text-green-500 bg-green-100'
|
|
||||||
};
|
|
||||||
const color = colors[params.value] || 'text-gray-500 bg-gray-100';
|
|
||||||
return (
|
|
||||||
<span className={`px-2 py-1 rounded-full text-xs font-semibold ${color}`}>
|
|
||||||
{params.value}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'priority',
|
|
||||||
headerName: 'Priority',
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 100,
|
|
||||||
headerClassName: 'datagrid--header',
|
|
||||||
cellClassName: 'datagrid--cell',
|
|
||||||
editable: hasUpdatePermission,
|
|
||||||
renderCell: (params) => {
|
|
||||||
const colors = {
|
|
||||||
'High': 'text-red-500 bg-red-100',
|
|
||||||
'Medium': 'text-yellow-600 bg-yellow-100',
|
|
||||||
'Low': 'text-blue-500 bg-blue-100'
|
|
||||||
};
|
|
||||||
const color = colors[params.value] || 'text-gray-500 bg-gray-100';
|
|
||||||
return (
|
|
||||||
<span className={`px-2 py-1 rounded-full text-xs font-semibold ${color}`}>
|
|
||||||
{params.value}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'due_date',
|
field: 'due_date',
|
||||||
headerName: 'DueDate',
|
headerName: 'DueDate',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 150,
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
editable: hasUpdatePermission,
|
editable: hasUpdatePermission,
|
||||||
|
|
||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
valueGetter: (params) => params.row.due_date ? new Date(params.row.due_date) : null,
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
renderCell: (params) => params.value ? moment(params.value).format('MMM DD, YYYY HH:mm') : '-'
|
new Date(params.row.due_date),
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'priority',
|
||||||
|
headerName: 'Priority',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: 'actions',
|
||||||
type: 'actions',
|
type: 'actions',
|
||||||
minWidth: 80,
|
minWidth: 30,
|
||||||
headerClassName: 'datagrid--header',
|
headerClassName: 'datagrid--header',
|
||||||
cellClassName: 'datagrid--cell',
|
cellClassName: 'datagrid--cell',
|
||||||
getActions: (params: GridRowParams) => {
|
getActions: (params: GridRowParams) => {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<div key={params?.row?.id}>
|
<div key={params?.row?.id}>
|
||||||
<ListActionsPopover
|
<ListActionsPopover
|
||||||
@ -96,7 +156,9 @@ export const loadColumns = async (
|
|||||||
itemId={params?.row?.id}
|
itemId={params?.row?.id}
|
||||||
pathEdit={`/tasks/tasks-edit/?id=${params?.row?.id}`}
|
pathEdit={`/tasks/tasks-edit/?id=${params?.row?.id}`}
|
||||||
pathView={`/tasks/tasks-view/?id=${params?.row?.id}`}
|
pathView={`/tasks/tasks-view/?id=${params?.row?.id}`}
|
||||||
|
|
||||||
hasUpdatePermission={hasUpdatePermission}
|
hasUpdatePermission={hasUpdatePermission}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>,
|
</div>,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { mdiChartTimelineVariant } from '@mdi/js'
|
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
@ -16,10 +16,21 @@ import FormField from '../../components/FormField'
|
|||||||
import BaseDivider from '../../components/BaseDivider'
|
import BaseDivider from '../../components/BaseDivider'
|
||||||
import BaseButtons from '../../components/BaseButtons'
|
import BaseButtons from '../../components/BaseButtons'
|
||||||
import BaseButton from '../../components/BaseButton'
|
import BaseButton from '../../components/BaseButton'
|
||||||
|
import FormCheckRadio from '../../components/FormCheckRadio'
|
||||||
|
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'
|
||||||
|
import FormFilePicker from '../../components/FormFilePicker'
|
||||||
|
import FormImagePicker from '../../components/FormImagePicker'
|
||||||
|
import { SelectField } from "../../components/SelectField";
|
||||||
|
import { SelectFieldMany } from "../../components/SelectFieldMany";
|
||||||
|
import { SwitchField } from '../../components/SwitchField'
|
||||||
|
import {RichTextField} from "../../components/RichTextField";
|
||||||
|
|
||||||
import { update, fetch } from '../../stores/tasks/tasksSlice'
|
import { update, fetch } from '../../stores/tasks/tasksSlice'
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import {saveFile} from "../../helpers/fileSaver";
|
||||||
|
import dataFormatter from '../../helpers/dataFormatter';
|
||||||
|
import ImageField from "../../components/ImageField";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -27,32 +38,199 @@ const EditTasksPage = () => {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const initVals = {
|
const initVals = {
|
||||||
title: '',
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
user: null,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'title': '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
description: '',
|
description: '',
|
||||||
status: 'Pending',
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
status: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
due_date: new Date(),
|
due_date: new Date(),
|
||||||
priority: 'Low',
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
priority: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
const [initialValues, setInitialValues] = useState(initVals)
|
const [initialValues, setInitialValues] = useState(initVals)
|
||||||
|
|
||||||
const { tasks } = useAppSelector((state) => state.tasks)
|
const { tasks } = useAppSelector((state) => state.tasks)
|
||||||
|
|
||||||
|
|
||||||
const { id } = router.query
|
const { id } = router.query
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
|
||||||
dispatch(fetch({ id: id }))
|
dispatch(fetch({ id: id }))
|
||||||
}
|
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tasks && typeof tasks === 'object') {
|
if (typeof tasks === 'object') {
|
||||||
setInitialValues({
|
setInitialValues(tasks)
|
||||||
title: tasks.title || '',
|
}
|
||||||
description: tasks.description || '',
|
}, [tasks])
|
||||||
status: tasks.status || 'Pending',
|
|
||||||
due_date: tasks.due_date ? new Date(tasks.due_date) : new Date(),
|
useEffect(() => {
|
||||||
priority: tasks.priority || 'Low',
|
if (typeof tasks === 'object') {
|
||||||
})
|
const newInitialVal = {...initVals};
|
||||||
|
Object.keys(initVals).forEach(el => newInitialVal[el] = (tasks)[el])
|
||||||
|
setInitialValues(newInitialVal);
|
||||||
}
|
}
|
||||||
}, [tasks])
|
}, [tasks])
|
||||||
|
|
||||||
@ -64,10 +242,10 @@ const EditTasksPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Edit Task')}</title>
|
<title>{getPageTitle('Edit tasks')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit Task'} main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit tasks'} main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
@ -78,52 +256,255 @@ const EditTasksPage = () => {
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<FormField label='User' labelFor='user'>
|
||||||
|
<Field
|
||||||
|
name='user'
|
||||||
|
id='user'
|
||||||
|
component={SelectField}
|
||||||
|
options={initialValues.user}
|
||||||
|
itemRef={'users'}
|
||||||
|
|
||||||
|
|
||||||
|
showField={'firstName'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Title"
|
label="Title"
|
||||||
required
|
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="title"
|
name="title"
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="Description" hasTextareaHeight>
|
<FormField label="Description" hasTextareaHeight>
|
||||||
<Field name="description" as="textarea" placeholder="Description" />
|
<Field name="description" as="textarea" placeholder="Description" />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="Status" labelFor="status">
|
<FormField label="Status" labelFor="status">
|
||||||
<Field name="status" id="status" component="select">
|
<Field name="status" id="status" component="select">
|
||||||
|
|
||||||
<option value="Pending">Pending</option>
|
<option value="Pending">Pending</option>
|
||||||
|
|
||||||
<option value="InProgress">InProgress</option>
|
<option value="InProgress">InProgress</option>
|
||||||
|
|
||||||
<option value="Done">Done</option>
|
<option value="Done">Done</option>
|
||||||
|
|
||||||
</Field>
|
</Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="DueDate"
|
label="DueDate"
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
dateFormat="yyyy-MM-dd HH:mm"
|
dateFormat="yyyy-MM-dd hh:mm"
|
||||||
showTimeSelect
|
showTimeSelect
|
||||||
selected={initialValues.due_date}
|
selected={initialValues.due_date ?
|
||||||
|
new Date(
|
||||||
|
dayjs(initialValues.due_date).format('YYYY-MM-DD hh:mm'),
|
||||||
|
) : null
|
||||||
|
}
|
||||||
onChange={(date) => setInitialValues({...initialValues, 'due_date': date})}
|
onChange={(date) => setInitialValues({...initialValues, 'due_date': date})}
|
||||||
className="w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 bg-white dark:bg-slate-800 border"
|
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="Priority" labelFor="priority">
|
<FormField label="Priority" labelFor="priority">
|
||||||
<Field name="priority" id="priority" component="select">
|
<Field name="priority" id="priority" component="select">
|
||||||
|
|
||||||
<option value="Low">Low</option>
|
<option value="Low">Low</option>
|
||||||
|
|
||||||
<option value="Medium">Medium</option>
|
<option value="Medium">Medium</option>
|
||||||
|
|
||||||
<option value="High">High</option>
|
<option value="High">High</option>
|
||||||
|
|
||||||
</Field>
|
</Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Save Changes" />
|
<BaseButton type="submit" color="info" label="Submit" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="Reset" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/tasks/tasks-list')}/>
|
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/tasks/tasks-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
@ -138,7 +519,9 @@ const EditTasksPage = () => {
|
|||||||
EditTasksPage.getLayout = function getLayout(page: ReactElement) {
|
EditTasksPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return (
|
return (
|
||||||
<LayoutAuthenticated
|
<LayoutAuthenticated
|
||||||
|
|
||||||
permission={'UPDATE_TASKS'}
|
permission={'UPDATE_TASKS'}
|
||||||
|
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</LayoutAuthenticated>
|
</LayoutAuthenticated>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getPageTitle } from '../../config'
|
|||||||
import TableTasks from '../../components/Tasks/TableTasks'
|
import TableTasks from '../../components/Tasks/TableTasks'
|
||||||
import BaseButton from '../../components/BaseButton'
|
import BaseButton from '../../components/BaseButton'
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Link from "next/link";
|
||||||
import {useAppDispatch, useAppSelector} from "../../stores/hooks";
|
import {useAppDispatch, useAppSelector} from "../../stores/hooks";
|
||||||
import CardBoxModal from "../../components/CardBoxModal";
|
import CardBoxModal from "../../components/CardBoxModal";
|
||||||
import DragDropFilePicker from "../../components/DragDropFilePicker";
|
import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||||
@ -24,6 +25,7 @@ const TasksTablesPage = () => {
|
|||||||
const [filterItems, setFilterItems] = useState([]);
|
const [filterItems, setFilterItems] = useState([]);
|
||||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||||
const [isModalActive, setIsModalActive] = useState(false);
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
|
const [showTableView, setShowTableView] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
@ -32,12 +34,17 @@ const TasksTablesPage = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
|
||||||
const [filters] = useState([
|
const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Description', title: 'description'},
|
||||||
{label: 'Title', title: 'title'},
|
|
||||||
{label: 'Description', title: 'description'},
|
|
||||||
{label: 'DueDate', title: 'due_date', date: 'true'},
|
{label: 'DueDate', title: 'due_date', date: 'true'},
|
||||||
{label: 'Status', title: 'status', type: 'enum', options: ['Pending','In Progress','Done']},
|
|
||||||
{label: 'Priority', title: 'priority', type: 'enum', options: ['Low','Medium','High']},
|
|
||||||
|
{label: 'User', title: 'user'},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{label: 'Status', title: 'status', type: 'enum', options: ['Pending','InProgress','Done']},{label: 'Priority', title: 'priority', type: 'enum', options: ['Low','Medium','High']},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_TASKS');
|
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_TASKS');
|
||||||
@ -86,12 +93,12 @@ const TasksTablesPage = () => {
|
|||||||
<title>{getPageTitle('Tasks')}</title>
|
<title>{getPageTitle('Tasks')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="My Tasks" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Tasks" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
|
||||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/tasks/tasks-new'} color='info' label='New Task'/>}
|
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/tasks/tasks-new'} color='info' label='New Item'/>}
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
className={'mr-3'}
|
className={'mr-3'}
|
||||||
@ -113,13 +120,17 @@ const TasksTablesPage = () => {
|
|||||||
<div id='delete-rows-button'></div>
|
<div id='delete-rows-button'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='md:inline-flex items-center ms-auto'>
|
||||||
|
<Link href={'/tasks/tasks-table'}>Switch to Table</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|
||||||
<TableTasks
|
<TableTasks
|
||||||
filterItems={filterItems}
|
filterItems={filterItems}
|
||||||
setFilterItems={setFilterItems}
|
setFilterItems={setFilterItems}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
showGrid={true} // Default to grid for better visibility
|
showGrid={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
@ -127,6 +138,7 @@ const TasksTablesPage = () => {
|
|||||||
title='Upload CSV'
|
title='Upload CSV'
|
||||||
buttonColor='info'
|
buttonColor='info'
|
||||||
buttonLabel={'Confirm'}
|
buttonLabel={'Confirm'}
|
||||||
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
isActive={isModalActive}
|
isActive={isModalActive}
|
||||||
onConfirm={onModalConfirm}
|
onConfirm={onModalConfirm}
|
||||||
onCancel={onModalCancel}
|
onCancel={onModalCancel}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { mdiChartTimelineVariant } from '@mdi/js'
|
import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import CardBox from '../../components/CardBox'
|
import CardBox from '../../components/CardBox'
|
||||||
@ -12,18 +12,121 @@ import FormField from '../../components/FormField'
|
|||||||
import BaseDivider from '../../components/BaseDivider'
|
import BaseDivider from '../../components/BaseDivider'
|
||||||
import BaseButtons from '../../components/BaseButtons'
|
import BaseButtons from '../../components/BaseButtons'
|
||||||
import BaseButton from '../../components/BaseButton'
|
import BaseButton from '../../components/BaseButton'
|
||||||
|
import FormCheckRadio from '../../components/FormCheckRadio'
|
||||||
|
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'
|
||||||
|
import FormFilePicker from '../../components/FormFilePicker'
|
||||||
|
import FormImagePicker from '../../components/FormImagePicker'
|
||||||
|
import { SwitchField } from '../../components/SwitchField'
|
||||||
|
|
||||||
|
import { SelectField } from '../../components/SelectField'
|
||||||
|
import { SelectFieldMany } from "../../components/SelectFieldMany";
|
||||||
|
import {RichTextField} from "../../components/RichTextField";
|
||||||
|
|
||||||
import { create } from '../../stores/tasks/tasksSlice'
|
import { create } from '../../stores/tasks/tasksSlice'
|
||||||
import { useAppDispatch } from '../../stores/hooks'
|
import { useAppDispatch } from '../../stores/hooks'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
user: '',
|
user: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
title: '',
|
title: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
description: '',
|
description: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
status: 'Pending',
|
status: 'Pending',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
due_date: '',
|
due_date: '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
priority: 'Low',
|
priority: 'Low',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -31,6 +134,9 @@ const TasksNew = () => {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (data) => {
|
const handleSubmit = async (data) => {
|
||||||
await dispatch(create(data))
|
await dispatch(create(data))
|
||||||
await router.push('/tasks/tasks-list')
|
await router.push('/tasks/tasks-list')
|
||||||
@ -38,62 +144,233 @@ const TasksNew = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('New Task')}</title>
|
<title>{getPageTitle('New Item')}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Task" main>
|
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main>
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
<CardBox>
|
<CardBox>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={
|
||||||
|
|
||||||
|
initialValues
|
||||||
|
|
||||||
|
}
|
||||||
onSubmit={(values) => handleSubmit(values)}
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<FormField label="User" labelFor="user">
|
||||||
|
<Field name="user" id="user" component={SelectField} options={[]} itemRef={'users'}></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Title"
|
label="Title"
|
||||||
required
|
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
name="title"
|
name="title"
|
||||||
placeholder="What needs to be done?"
|
placeholder="Title"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField label="Description (Optional)" hasTextareaHeight>
|
|
||||||
<Field name="description" as="textarea" placeholder="More details about this task..." />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<FormField label="Description" hasTextareaHeight>
|
||||||
|
<Field name="description" as="textarea" placeholder="Description" />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="Status" labelFor="status">
|
<FormField label="Status" labelFor="status">
|
||||||
<Field name="status" id="status" component="select">
|
<Field name="status" id="status" component="select">
|
||||||
|
|
||||||
<option value="Pending">Pending</option>
|
<option value="Pending">Pending</option>
|
||||||
|
|
||||||
<option value="InProgress">InProgress</option>
|
<option value="InProgress">InProgress</option>
|
||||||
|
|
||||||
<option value="Done">Done</option>
|
<option value="Done">Done</option>
|
||||||
|
|
||||||
</Field>
|
</Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="DueDate"
|
label="DueDate"
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
name="due_date"
|
name="due_date"
|
||||||
|
placeholder="DueDate"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<FormField label="Priority" labelFor="priority">
|
<FormField label="Priority" labelFor="priority">
|
||||||
<Field name="priority" id="priority" component="select">
|
<Field name="priority" id="priority" component="select">
|
||||||
|
|
||||||
<option value="Low">Low</option>
|
<option value="Low">Low</option>
|
||||||
|
|
||||||
<option value="Medium">Medium</option>
|
<option value="Medium">Medium</option>
|
||||||
|
|
||||||
<option value="High">High</option>
|
<option value="High">High</option>
|
||||||
|
|
||||||
</Field>
|
</Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type="submit" color="info" label="Create Task" />
|
<BaseButton type="submit" color="info" label="Submit" />
|
||||||
<BaseButton type="reset" color="info" outline label="Reset" />
|
<BaseButton type="reset" color="info" outline label="Reset" />
|
||||||
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/tasks/tasks-list')}/>
|
<BaseButton type='reset' color='danger' outline label='Cancel' onClick={() => router.push('/tasks/tasks-list')}/>
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
@ -108,7 +385,9 @@ const TasksNew = () => {
|
|||||||
TasksNew.getLayout = function getLayout(page: ReactElement) {
|
TasksNew.getLayout = function getLayout(page: ReactElement) {
|
||||||
return (
|
return (
|
||||||
<LayoutAuthenticated
|
<LayoutAuthenticated
|
||||||
|
|
||||||
permission={'CREATE_TASKS'}
|
permission={'CREATE_TASKS'}
|
||||||
|
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</LayoutAuthenticated>
|
</LayoutAuthenticated>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user