Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
1b0f675e27 el pepe 2026-03-03 17:47:01 +00:00
Flatlogic Bot
b0ea840349 502.html 2026-03-03 17:46:41 +00:00
9 changed files with 164 additions and 1022 deletions

View File

@ -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>Service Starting</title> <title>Login</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>Gestor de Tareas Personales</h2> <h2>Web page for saving and administrating time (UTTN RA2)</h2>
<p>Aplicación web para gestionar tareas personales con registro/login y CRUD con filtros por estado y prioridad.</p> <p>In this web page we're gonna see a small web page with login, Dashboard & CRUD </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="100%" height="315" controls loop> <video width="20%" height="67" 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">

View File

@ -1,4 +1,3 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file'); const FileDBApi = require('./file');
const crypto = require('crypto'); const crypto = require('crypto');
@ -33,7 +32,7 @@ module.exports = class TasksDBApi {
status: data.status status: data.status
|| ||
null 'Pending'
, ,
due_date: data.due_date due_date: data.due_date
@ -43,26 +42,17 @@ module.exports = class TasksDBApi {
priority: data.priority priority: data.priority
|| ||
null 'Low'
, ,
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;
} }
@ -87,7 +77,7 @@ module.exports = class TasksDBApi {
status: item.status status: item.status
|| ||
null 'Pending'
, ,
due_date: item.due_date due_date: item.due_date
@ -97,21 +87,19 @@ module.exports = class TasksDBApi {
priority: item.priority priority: item.priority
|| ||
null 'Low'
, ,
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;
} }
@ -122,47 +110,22 @@ 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;
} }
@ -175,6 +138,7 @@ module.exports = class TasksDBApi {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
}, },
userId: currentUser.id, // Only allow deleting own tasks
}, },
transaction, transaction,
}); });
@ -199,7 +163,14 @@ 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.findByPk(id, options); const tasks = await db.tasks.findOne({
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
@ -228,18 +199,6 @@ 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;
} }
@ -252,9 +211,10 @@ 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;
@ -262,28 +222,7 @@ module.exports = class TasksDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
let include = [ let include = []; // Removed User include for privacy/simplicity in personal manager
{
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) {
@ -317,10 +256,6 @@ module.exports = class TasksDBApi {
} }
if (filter.due_dateRange) { if (filter.due_dateRange) {
const [start, end] = filter.due_dateRange; const [start, end] = filter.due_dateRange;
@ -369,11 +304,6 @@ module.exports = class TasksDBApi {
} }
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;
@ -400,8 +330,6 @@ module.exports = class TasksDBApi {
} }
const queryOptions = { const queryOptions = {
where, where,
include, include,
@ -410,7 +338,6 @@ 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) {
@ -434,8 +361,6 @@ 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]: [
@ -464,5 +389,4 @@ module.exports = class TasksDBApi {
} }
}; };

View File

@ -0,0 +1,26 @@
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
}
};

View File

@ -16,61 +16,35 @@ 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", "In Progress",
"Done"
"InProgress",
"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",
"High"
"Medium",
"High"
], ],
defaultValue: "Low",
}, },
importHash: { importHash: {
@ -87,20 +61,6 @@ 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: {
@ -109,9 +69,6 @@ priority: {
constraints: false, constraints: false,
}); });
db.tasks.belongsTo(db.users, { db.tasks.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
@ -121,9 +78,5 @@ priority: {
}); });
}; };
return tasks; return tasks;
}; };

View File

@ -19,7 +19,6 @@ import {dataGridStyles} from "../../styles";
import KanbanBoard from '../KanbanBoard/KanbanBoard'; import KanbanBoard from '../KanbanBoard/KanbanBoard';
import axios from 'axios';
const perPage = 10 const perPage = 10
@ -30,7 +29,6 @@ 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('');
@ -51,10 +49,6 @@ 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);
@ -83,35 +77,22 @@ 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: "In Progress", label: "In Progress" },
{ 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)
@ -159,18 +140,14 @@ 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) => {
@ -190,9 +167,7 @@ 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) => {
@ -502,4 +477,4 @@ const TableSampleTasks = ({ filterItems, setFilterItems, filters, showGrid }) =>
) )
} }
export default TableSampleTasks export default TableSampleTasks

View File

@ -1,168 +1,106 @@
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: 1, flex: 2,
minWidth: 120, minWidth: 200,
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: 'due_date',
headerName: 'DueDate',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.due_date),
},
{ {
field: 'priority', field: 'priority',
headerName: 'Priority', headerName: 'Priority',
flex: 1, flex: 1,
minWidth: 120, minWidth: 100,
filterable: false,
headerClassName: 'datagrid--header', headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell', cellClassName: 'datagrid--cell',
editable: hasUpdatePermission, 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',
headerName: 'Due Date',
flex: 1,
minWidth: 150,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params) => params.row.due_date ? new Date(params.row.due_date) : null,
renderCell: (params) => params.value ? moment(params.value).format('MMM DD, YYYY HH:mm') : '-'
}, },
{ {
field: 'actions', field: 'actions',
type: 'actions', type: 'actions',
minWidth: 30, minWidth: 80,
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
onDelete={onDelete} onDelete={onDelete}
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>,
] ]
}, },
}, },
]; ];
}; };

View File

@ -1,4 +1,4 @@
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' import { mdiChartTimelineVariant } 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,21 +16,10 @@ 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";
@ -38,202 +27,35 @@ 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(() => {
dispatch(fetch({ id: id })) if (id) {
dispatch(fetch({ id: id }))
}
}, [id]) }, [id])
useEffect(() => { useEffect(() => {
if (typeof tasks === 'object') { if (tasks && typeof tasks === 'object') {
setInitialValues(tasks) setInitialValues({
title: tasks.title || '',
description: tasks.description || '',
status: tasks.status || 'Pending',
due_date: tasks.due_date ? new Date(tasks.due_date) : new Date(),
priority: tasks.priority || 'Low',
})
} }
}, [tasks]) }, [tasks])
useEffect(() => {
if (typeof tasks === 'object') {
const newInitialVal = {...initVals};
Object.keys(initVals).forEach(el => newInitialVal[el] = (tasks)[el])
setInitialValues(newInitialVal);
}
}, [tasks])
const handleSubmit = async (data) => { const handleSubmit = async (data) => {
await dispatch(update({ id: id, data })) await dispatch(update({ id: id, data }))
await router.push('/tasks/tasks-list') await router.push('/tasks/tasks-list')
@ -242,10 +64,10 @@ const EditTasksPage = () => {
return ( return (
<> <>
<Head> <Head>
<title>{getPageTitle('Edit tasks')}</title> <title>{getPageTitle('Edit Task')}</title>
</Head> </Head>
<SectionMain> <SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit tasks'} main> <SectionTitleLineWithButton icon={mdiChartTimelineVariant} title={'Edit Task'} main>
{''} {''}
</SectionTitleLineWithButton> </SectionTitleLineWithButton>
<CardBox> <CardBox>
@ -256,255 +78,52 @@ 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="In Progress">In Progress</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="Due Date"
> >
<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="Submit" /> <BaseButton type="submit" color="info" label="Save Changes" />
<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>
@ -519,13 +138,11 @@ 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>
) )
} }
export default EditTasksPage export default EditTasksPage

View File

@ -10,7 +10,6 @@ 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";
@ -25,7 +24,6 @@ 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);
@ -34,17 +32,12 @@ const TasksTablesPage = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Description', title: 'description'}, const [filters] = useState([
{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');
@ -93,12 +86,12 @@ const TasksTablesPage = () => {
<title>{getPageTitle('Tasks')}</title> <title>{getPageTitle('Tasks')}</title>
</Head> </Head>
<SectionMain> <SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Tasks" main> <SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="My 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 Item'/>} {hasCreatePermission && <BaseButton className={'mr-3'} href={'/tasks/tasks-new'} color='info' label='New Task'/>}
<BaseButton <BaseButton
className={'mr-3'} className={'mr-3'}
@ -120,17 +113,13 @@ 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={false} showGrid={true} // Default to grid for better visibility
/> />
</SectionMain> </SectionMain>
@ -138,7 +127,6 @@ 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}
@ -165,4 +153,4 @@ TasksTablesPage.getLayout = function getLayout(page: ReactElement) {
) )
} }
export default TasksTablesPage export default TasksTablesPage

View File

@ -1,4 +1,4 @@
import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js' import { mdiChartTimelineVariant } 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,121 +12,18 @@ 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',
} }
@ -134,9 +31,6 @@ 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')
@ -144,233 +38,62 @@ const TasksNew = () => {
return ( return (
<> <>
<Head> <Head>
<title>{getPageTitle('New Item')}</title> <title>{getPageTitle('New Task')}</title>
</Head> </Head>
<SectionMain> <SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Item" main> <SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="New Task" 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="Title" placeholder="What needs to be done?"
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="In Progress">In Progress</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="Due Date"
> >
<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="Submit" /> <BaseButton type="submit" color="info" label="Create Task" />
<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>
@ -385,13 +108,11 @@ 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>
) )
} }
export default TasksNew export default TasksNew