@imj_media/tareas 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +8 -0
- package/.storybook/main.ts +26 -0
- package/.storybook/preview.ts +15 -0
- package/LICENSE.md +21 -0
- package/README.md +50 -0
- package/core/actions/get_all_users.action.ts +16 -0
- package/core/actions/get_salesman_response.action.ts +16 -0
- package/core/actions/get_tasks_project.action.ts +62 -0
- package/core/actions/get_tasks_response.action.ts +56 -0
- package/eslint.config.js +28 -0
- package/global.d.ts +3 -0
- package/infraestructure/interfaces/salesmans-obp-response.ts +43 -0
- package/infraestructure/interfaces/salesmans-obp.ts +4 -0
- package/infraestructure/interfaces/tasks-campania-response.ts +118 -0
- package/infraestructure/interfaces/tasks-campania.ts +26 -0
- package/infraestructure/interfaces/tasks-kanban-general.ts +25 -0
- package/infraestructure/interfaces/tasks-reponse.ts +111 -0
- package/infraestructure/interfaces/teams-response.ts +7 -0
- package/infraestructure/interfaces/teams.ts +5 -0
- package/infraestructure/interfaces/users-obp-response.ts +52 -0
- package/infraestructure/interfaces/users.ts +5 -0
- package/infraestructure/mappers/all-users-obp.ts +12 -0
- package/infraestructure/mappers/campaign-tasks.ts +35 -0
- package/infraestructure/mappers/kanban-tasks.ts +35 -0
- package/infraestructure/mappers/salesmans-obp.ts +11 -0
- package/infraestructure/mappers/teams.ts +12 -0
- package/package.json +61 -0
- package/postcss.config.js +6 -0
- package/src/components/atoms/Avatar.tsx +14 -0
- package/src/components/atoms/Comment.tsx +33 -0
- package/src/components/atoms/InputSearch.tsx +40 -0
- package/src/components/atoms/SkeletonCard.tsx +17 -0
- package/src/components/atoms/TabButton.tsx +16 -0
- package/src/components/atoms/TooltipUser.tsx +26 -0
- package/src/components/atoms/index.ts +2 -0
- package/src/components/index.ts +3 -0
- package/src/components/kanban-campania/DoneBoard.tsx +12 -0
- package/src/components/kanban-campania/KanbanCampania.tsx +39 -0
- package/src/components/kanban-campania/ToDoBoard.tsx +12 -0
- package/src/components/kanban-campania/WorkingBoard.tsx +13 -0
- package/src/components/kanban-campania/filters.ts +46 -0
- package/src/components/kanban-campania/index.ts +3 -0
- package/src/components/kanban-general/DoneBoard.tsx +12 -0
- package/src/components/kanban-general/KanbanGeneral.tsx +40 -0
- package/src/components/kanban-general/ToDoBoard.tsx +12 -0
- package/src/components/kanban-general/WorkingBoard.tsx +13 -0
- package/src/components/kanban-general/filters.ts +58 -0
- package/src/components/kanban-general/index.ts +3 -0
- package/src/components/layout/FilterButton.tsx +50 -0
- package/src/components/layout/FilterContent.tsx +70 -0
- package/src/components/layout/IndexComponents.tsx +32 -0
- package/src/components/lista-campania/ChildTask.tsx +22 -0
- package/src/components/lista-campania/Date.tsx +30 -0
- package/src/components/lista-campania/ListaCampania.tsx +21 -0
- package/src/components/lista-campania/ParentTask.tsx +57 -0
- package/src/components/molecules/AllComments.tsx +78 -0
- package/src/components/molecules/ButtonAssignUsers.tsx +175 -0
- package/src/components/molecules/DependentTasks.tsx +64 -0
- package/src/components/molecules/Tabs.tsx +39 -0
- package/src/components/molecules/index.ts +1 -0
- package/src/components/organisms/Board.tsx +87 -0
- package/src/components/organisms/Checkbox.tsx +45 -0
- package/src/components/organisms/DetailsTask.tsx +286 -0
- package/src/components/organisms/TabDetailsTask.tsx +39 -0
- package/src/components/organisms/Task.tsx +176 -0
- package/src/components/organisms/index.ts +2 -0
- package/src/components/tasks/PriorityButton.tsx +79 -0
- package/src/components/templates/Layout.tsx +84 -0
- package/src/components/templates/TableList/components/TableList.scss +270 -0
- package/src/components/templates/TableList/components/TableList.tsx +239 -0
- package/src/components/templates/TableList/components/index.tsx +1 -0
- package/src/constants/colors.ts +64 -0
- package/src/constants/gaps.ts +8 -0
- package/src/constants/paddings.ts +8 -0
- package/src/constants/shadows.ts +5 -0
- package/src/context/filtersLayout.context.tsx +118 -0
- package/src/context/kanbanCampania.context.tsx +50 -0
- package/src/context/useApis.context.tsx +47 -0
- package/src/context/userLog.context.tsx +50 -0
- package/src/env.d.ts +10 -0
- package/src/functions/taskCalculations.tsx +818 -0
- package/src/hooks/useAllUsers.ts +18 -0
- package/src/hooks/useCheckTask.tsx +15 -0
- package/src/hooks/useComerciales.ts +58 -0
- package/src/hooks/useDoneTasks.ts +90 -0
- package/src/hooks/useElementPosition.ts +34 -0
- package/src/hooks/useFunctionsTasks.ts +57 -0
- package/src/hooks/useNormalizedData.ts +36 -0
- package/src/hooks/useTeams.ts +19 -0
- package/src/hooks/useToDoTasks.ts +89 -0
- package/src/hooks/useWorkingTasks.ts +85 -0
- package/src/index.css +55 -0
- package/src/index.ts +2 -0
- package/src/index.tsx +1 -0
- package/src/pages/App.tsx +42 -0
- package/src/pages/NoAccessToken.tsx +20 -0
- package/src/pages/NoUser.tsx +20 -0
- package/src/stories/AppTasks.stories.tsx +36 -0
- package/src/stories/Table.stories.tsx +160 -0
- package/src/types/index.ts +107 -0
- package/src/types/interfaces.ts +67 -0
- package/src/types/layout.types.ts +30 -0
- package/src/utils/filters.functions.ts +17 -0
- package/src/utils/formats.ts +33 -0
- package/src/utils/functionsStorybook.tsx +0 -0
- package/src/utils/inputs.functions.ts +25 -0
- package/src/utils/tanstack.functions.ts +19 -0
- package/src/utils/utils.ts +12 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +31 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import TabButton from "../atoms/TabButton"
|
|
3
|
+
import { TTab } from "../../types/layout.types";
|
|
4
|
+
|
|
5
|
+
interface ITabsProps {
|
|
6
|
+
tabs: TTab[];
|
|
7
|
+
renderItem: (index: TTab) => void;
|
|
8
|
+
initialTab?: TTab;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Tabs = ({tabs, renderItem, initialTab}: ITabsProps) => {
|
|
12
|
+
const [activeTab, setActiveTab] = useState<TTab>(initialTab ?? tabs[0].toLowerCase() as TTab);
|
|
13
|
+
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
if(initialTab) { setActiveTab(initialTab) }
|
|
16
|
+
},[initialTab])
|
|
17
|
+
|
|
18
|
+
const onToggleTab = (index: TTab) => {
|
|
19
|
+
setActiveTab(index);
|
|
20
|
+
renderItem(index);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex">
|
|
25
|
+
{tabs.map((tab: string, index: number) => (
|
|
26
|
+
<div className={`${index === 0 ? "border-l" : ""} border-r border-texts-placeholder px-m`}>
|
|
27
|
+
<TabButton
|
|
28
|
+
key={index}
|
|
29
|
+
onClick={() => onToggleTab(tab.toLowerCase() as TTab)}
|
|
30
|
+
label={tab}
|
|
31
|
+
active={tab.toLowerCase() === activeTab}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default Tabs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default as ButtonAssignUsers} from "./ButtonAssignUsers";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { TasksKanbanGeneral } from "../../../infraestructure/interfaces/tasks-kanban-general";
|
|
3
|
+
import { Icons } from "@imj_media/tasks-modules";
|
|
4
|
+
import Task from "./Task";
|
|
5
|
+
import SkeletonCard from "../atoms/SkeletonCard";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface IBoard {
|
|
9
|
+
title: string;
|
|
10
|
+
tasks: TasksKanbanGeneral[];
|
|
11
|
+
loadNextPage?: () => void;
|
|
12
|
+
isFetching?: boolean;
|
|
13
|
+
isLoadingData?: boolean;
|
|
14
|
+
total?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Board = ({
|
|
18
|
+
title,
|
|
19
|
+
tasks,
|
|
20
|
+
loadNextPage,
|
|
21
|
+
isFetching,
|
|
22
|
+
total
|
|
23
|
+
}: IBoard) => {
|
|
24
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
25
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
26
|
+
//* this ref is used to prevent the scroll from being triggered multiple times
|
|
27
|
+
const isLoading = useRef(false);
|
|
28
|
+
|
|
29
|
+
//* this useEffect is used to set the min height of the container
|
|
30
|
+
useEffect(()=>{
|
|
31
|
+
if(ref?.current && containerRef?.current){
|
|
32
|
+
containerRef.current.style.minHeight = `${ref?.current?.clientHeight - 50}px`;
|
|
33
|
+
}
|
|
34
|
+
},[ref?.current])
|
|
35
|
+
|
|
36
|
+
//* Load next page when scroll is at 95% of the container will be load more tasks
|
|
37
|
+
const onScroll = (event: any) => {
|
|
38
|
+
if (isLoading.current) return;
|
|
39
|
+
const {scrollTop, clientHeight, scrollHeight} = event?.target;
|
|
40
|
+
|
|
41
|
+
const scrollProgress = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
|
42
|
+
if (scrollProgress <= 95) return;
|
|
43
|
+
|
|
44
|
+
if(scrollProgress >= 95){
|
|
45
|
+
isLoading.current = true;
|
|
46
|
+
loadNextPage && loadNextPage();
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
isLoading.current = false;
|
|
49
|
+
}, 200);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div ref={ref} className={`flex flex-col bg-containers w-fit max-w-[400px] min-w-[400px] h-full min-h-[100%] pb-[200px] relative p-[16px] rounded-lg overflow-hidden border-t-4 border-containers`}>
|
|
55
|
+
<div className="flex justify-between items-center">
|
|
56
|
+
<div className="flex items-center gap-[8px]">
|
|
57
|
+
{/* //TODO: Add functionality when the release is ready to compress the board
|
|
58
|
+
<button className="text-base" title="Estamos trabajando esto para ustedes">
|
|
59
|
+
↓
|
|
60
|
+
</button>
|
|
61
|
+
*/}
|
|
62
|
+
<p className="text-base">{title}</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex items-center gap-[8px]">
|
|
65
|
+
{/* //TODO: Add plus button functionality when the release is ready
|
|
66
|
+
<button className="text-base">
|
|
67
|
+
<Icons icon="plus_outline"/>
|
|
68
|
+
</button>
|
|
69
|
+
*/}
|
|
70
|
+
<div className="flex items-center gap-[4px]">
|
|
71
|
+
<Icons icon="group_files"/>
|
|
72
|
+
<p>{total ?? 0}</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div ref={containerRef} className={`flex flex-col gap-[12px] overflow-y-auto py-4`} onScroll={onScroll} >
|
|
77
|
+
{tasks?.map(task => <Task key={task.id} {...task} /> )}
|
|
78
|
+
{ isFetching ?
|
|
79
|
+
<SkeletonCard />
|
|
80
|
+
: <p className="text-center text-gray-500">No hay más tareas por mostrar...</p>
|
|
81
|
+
}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default Board;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Icons, Tooltip } from '@imj_media/tasks-modules';
|
|
2
|
+
import useCheckTask from '../../hooks/useCheckTask';
|
|
3
|
+
import { COLORS } from '../../constants/colors';
|
|
4
|
+
|
|
5
|
+
interface ICheckboxProps {
|
|
6
|
+
id: number;
|
|
7
|
+
nameRequiredTask: string;
|
|
8
|
+
statusRequiredTask: number;
|
|
9
|
+
status: number;
|
|
10
|
+
}
|
|
11
|
+
const Checkbox = ({id, nameRequiredTask, statusRequiredTask, status}: ICheckboxProps) => {
|
|
12
|
+
const { checked, checkTask } = useCheckTask({id});
|
|
13
|
+
let checkboxId = Math.random().toString();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
{status === 1 && (
|
|
18
|
+
statusRequiredTask < 2 && nameRequiredTask !== "" ?
|
|
19
|
+
<Tooltip dispatch={
|
|
20
|
+
<label htmlFor={checkboxId}>
|
|
21
|
+
<Icons icon='info_circle' color={COLORS.warning.regular} strokeWidth={4}/>
|
|
22
|
+
</label>
|
|
23
|
+
}>
|
|
24
|
+
<div className='px-m py-s max-w-[200px] bg-warning-light rounded-lg border-l-[5px] border-warning-regular text-warning-dark text-xs'>
|
|
25
|
+
<p className='font-bold'>
|
|
26
|
+
Requiere completar su tarea requerida:
|
|
27
|
+
</p>
|
|
28
|
+
<p> { nameRequiredTask } </p>
|
|
29
|
+
</div>
|
|
30
|
+
</Tooltip> :
|
|
31
|
+
<label htmlFor={checkboxId} className={checked?'bg-success-regular rounded-full w-[20px] h-[20px] flex items-center justify-center mx-[2px] transition-all':" transition-colors"}>
|
|
32
|
+
<Icons
|
|
33
|
+
icon={checked ? 'check_outline' : 'circle_checked' }
|
|
34
|
+
color={ checked ? "white" : COLORS.texts.placeholder }
|
|
35
|
+
strokeWidth={4}
|
|
36
|
+
size={ checked ? 'xs' : 's' }
|
|
37
|
+
/>
|
|
38
|
+
</label>
|
|
39
|
+
)}
|
|
40
|
+
<input id={checkboxId} checked={checked} onChange={checkTask} type="checkbox" className="hidden"/>
|
|
41
|
+
</>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default Checkbox
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useApis } from '../../context/useApis.context';
|
|
3
|
+
import { Icons } from '@imj_media/tasks-modules';
|
|
4
|
+
import { format } from 'date-fns';
|
|
5
|
+
import { es } from 'date-fns/locale';
|
|
6
|
+
import { DetailsTaskData, DetailsTaskProps, TabDetailsTaskItem } from '../../types';
|
|
7
|
+
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
Accordion,
|
|
12
|
+
AccordionContent,
|
|
13
|
+
AccordionItem,
|
|
14
|
+
AccordionTrigger,
|
|
15
|
+
Avatar,
|
|
16
|
+
AvatarImage,
|
|
17
|
+
AvatarFallback,
|
|
18
|
+
} from '@imj_media/imj-ui';
|
|
19
|
+
|
|
20
|
+
import TabDetailsTask from './TabDetailsTask';
|
|
21
|
+
import AllComments from '../molecules/AllComments';
|
|
22
|
+
import { useUser } from '../../context/userLog.context';
|
|
23
|
+
import { getInitials } from '../../utils/utils';
|
|
24
|
+
import DependentTasks from '../molecules/DependentTasks';
|
|
25
|
+
|
|
26
|
+
const URL_IMAGES = 'https://devobp.imjmedia.com.mx';
|
|
27
|
+
|
|
28
|
+
export default function DetailsTask({ isOpen, setIsOpen, taskId }: DetailsTaskProps) {
|
|
29
|
+
const [data, setData] = useState<DetailsTaskData | null>(null);
|
|
30
|
+
const [addComment, setAddComment] = useState<boolean>(false);
|
|
31
|
+
const [currentUser, setCurrentUser] = useState<any | null>(null);
|
|
32
|
+
|
|
33
|
+
const { tasks_api } = useApis();
|
|
34
|
+
const { user } = useUser();
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if ((isOpen && taskId) || addComment) {
|
|
38
|
+
const getTaskDetails = async () => {
|
|
39
|
+
const response = await tasks_api.get(`/api/detalleTarea/${taskId}`);
|
|
40
|
+
setData(response.data);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getActualUser = async () => {
|
|
44
|
+
const userApi = await tasks_api.get(`/api/users/${user.id}`);
|
|
45
|
+
const response = await tasks_api.post(`/api/obtenerUsuarioOBP/`, {
|
|
46
|
+
emailList: [userApi?.data?.email],
|
|
47
|
+
});
|
|
48
|
+
setCurrentUser(response?.data?.data[0]);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
getTaskDetails();
|
|
52
|
+
getActualUser();
|
|
53
|
+
}
|
|
54
|
+
}, [isOpen, taskId, addComment]);
|
|
55
|
+
|
|
56
|
+
const priorityMap: any = {
|
|
57
|
+
0: {
|
|
58
|
+
label: 'Sin prioridad',
|
|
59
|
+
color: 'text-primary-regular',
|
|
60
|
+
iconColor: '#425CAC',
|
|
61
|
+
},
|
|
62
|
+
1: {
|
|
63
|
+
label: 'Baja',
|
|
64
|
+
color: 'text-success-regular',
|
|
65
|
+
iconColor: '#43B071',
|
|
66
|
+
},
|
|
67
|
+
2: {
|
|
68
|
+
label: 'Media',
|
|
69
|
+
color: 'text-warning-regular',
|
|
70
|
+
iconColor: '#D38A31',
|
|
71
|
+
},
|
|
72
|
+
3: {
|
|
73
|
+
label: 'Alta',
|
|
74
|
+
color: 'text-danger-regular',
|
|
75
|
+
iconColor: '#C34335',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const dateFormated = (date: Date | null) => {
|
|
80
|
+
if (date) {
|
|
81
|
+
return format(date, 'd MMM yyyy, h:mmaaa', {
|
|
82
|
+
locale: es,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return 'Fecha no disponible';
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const items: TabDetailsTaskItem[] = [
|
|
89
|
+
{
|
|
90
|
+
id: 1,
|
|
91
|
+
label: 'Todo',
|
|
92
|
+
component: (
|
|
93
|
+
<AllComments
|
|
94
|
+
taskId={taskId}
|
|
95
|
+
currentUser={currentUser}
|
|
96
|
+
data={data}
|
|
97
|
+
onNewComment={setAddComment}
|
|
98
|
+
/>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 2,
|
|
103
|
+
label: 'Dependencias',
|
|
104
|
+
component: <DependentTasks data={data?.ids_t_dependientes} />,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<>
|
|
110
|
+
<>
|
|
111
|
+
<div
|
|
112
|
+
className={`fixed inset-0 bg-transparent bg-opacity-50 flex justify-center items-center z-20 ${
|
|
113
|
+
isOpen ? 'pointer-events-auto block' : 'pointer-events-none hidden'
|
|
114
|
+
}`}
|
|
115
|
+
onClick={() => setIsOpen(false)}
|
|
116
|
+
/>
|
|
117
|
+
<section
|
|
118
|
+
className={`fixed top-0 bottom-0 bg-white bg-opacity-50 flex justify-center items-center w-[40rem] h-screen z-50 overflow-hidden transition-all ease-in-out duration-500 shadow-2xl ${
|
|
119
|
+
isOpen
|
|
120
|
+
? 'right-0 pointer-events-auto block'
|
|
121
|
+
: 'right-[-100%] pointer-events-none hidden'
|
|
122
|
+
}`}
|
|
123
|
+
>
|
|
124
|
+
<div className="flex w-full h-full flex-col overflow-y-scroll bg-white border-none shadow-xl px-4 py-6">
|
|
125
|
+
<div className="w-full flex flex-row justify-between pb-4 gap-10 border-none">
|
|
126
|
+
<h3 className="text-lg font-semibold text-neutral-900">
|
|
127
|
+
{data?.texto_corto}
|
|
128
|
+
</h3>
|
|
129
|
+
<Button
|
|
130
|
+
variant="shadow"
|
|
131
|
+
size="sm"
|
|
132
|
+
onClick={() => setIsOpen(false)}
|
|
133
|
+
className="bg-neutral-100 w-10 h-10"
|
|
134
|
+
>
|
|
135
|
+
<Icons icon="x_outline" />
|
|
136
|
+
</Button>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="w-full flex flex-row justify-between">
|
|
139
|
+
<span className="text-neutral-600 text-sm">
|
|
140
|
+
{data?.campania} / {data?.nombre_medio}
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="w-full flex flex-col gap-3 mt-4">
|
|
144
|
+
<h6 className="text-neutral-900 font-semibold text-base">
|
|
145
|
+
Descripción
|
|
146
|
+
</h6>
|
|
147
|
+
<p className="text-neutral-600 font-normal text-sm">
|
|
148
|
+
{data?.texto_largo ?? 'Sin descripción'}
|
|
149
|
+
</p>
|
|
150
|
+
{/* <Textarea
|
|
151
|
+
className="w-full border-none px-0 min-h-[20px] max-h-[200px] font-normal placeholder:font-normal focus:outline-none focus:ring-neutral-100 resize-none"
|
|
152
|
+
placeholder="Agregar descripción"
|
|
153
|
+
/> */}
|
|
154
|
+
</div>
|
|
155
|
+
<Accordion
|
|
156
|
+
type="single"
|
|
157
|
+
collapsible
|
|
158
|
+
defaultValue="item-1"
|
|
159
|
+
className="w-full flex flex-col border border-neutral-200 rounded-xl mt-4"
|
|
160
|
+
>
|
|
161
|
+
<AccordionItem value="item-1" className="border-none">
|
|
162
|
+
<AccordionTrigger className="text-neutral-900 font-medium text-sm w-full flex flex-row border-b border-neutral-200 p-4 ">
|
|
163
|
+
Detalles
|
|
164
|
+
</AccordionTrigger>
|
|
165
|
+
<AccordionContent className="border-none">
|
|
166
|
+
<div className="w-full grid grid-cols-3 gap-4 p-4">
|
|
167
|
+
<div className="flex flex-col w-auto gap-2">
|
|
168
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
169
|
+
Responsable
|
|
170
|
+
</span>
|
|
171
|
+
{data?.responsable?.nombre ? (
|
|
172
|
+
<div className="flex flex-row gap-2 items-center">
|
|
173
|
+
<Avatar className="w-8 h-8">
|
|
174
|
+
<AvatarImage
|
|
175
|
+
src={`${URL_IMAGES}${data?.responsable?.imagen?.formats?.thumbnail?.url}`}
|
|
176
|
+
alt="@shadcn"
|
|
177
|
+
/>
|
|
178
|
+
<AvatarFallback>
|
|
179
|
+
{getInitials(data?.responsable?.nombre)}
|
|
180
|
+
</AvatarFallback>
|
|
181
|
+
</Avatar>
|
|
182
|
+
<span className="text-xs text-neutral-600 font-normal">
|
|
183
|
+
{data?.responsable?.nombre}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
) : (
|
|
187
|
+
<span className="text-xs text-neutral-600 font-normal">
|
|
188
|
+
Sin responsable
|
|
189
|
+
</span>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
<div className="flex flex-col w-auto gap-2">
|
|
193
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
194
|
+
Equipo asignado
|
|
195
|
+
</span>
|
|
196
|
+
<div className="flex flex-row gap-2">
|
|
197
|
+
<span className="text-xs text-neutral-600 font-normal">
|
|
198
|
+
{data?.equipo?.nombre ?? 'Sin equipo'}
|
|
199
|
+
</span>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div className="flex flex-col w-auto gap-2">
|
|
203
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
204
|
+
Prioridad
|
|
205
|
+
</span>
|
|
206
|
+
<div className="flex flex-row items-center gap-2">
|
|
207
|
+
<Icons
|
|
208
|
+
icon="flag"
|
|
209
|
+
size="xs"
|
|
210
|
+
strokeWidth={5}
|
|
211
|
+
color={
|
|
212
|
+
priorityMap[data?.prioridad as any]
|
|
213
|
+
?.iconColor
|
|
214
|
+
}
|
|
215
|
+
/>
|
|
216
|
+
<span
|
|
217
|
+
className={`text-sm text-neutral-600 font-normal ${
|
|
218
|
+
priorityMap[data?.prioridad as any]?.color
|
|
219
|
+
}`}
|
|
220
|
+
>
|
|
221
|
+
{priorityMap[data?.prioridad as any]?.label ??
|
|
222
|
+
'Sin prioridad'}
|
|
223
|
+
</span>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="flex flex-col w-auto gap-2">
|
|
227
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
228
|
+
Lista
|
|
229
|
+
</span>
|
|
230
|
+
<div className="flex flex-row gap-2">
|
|
231
|
+
<span className="text-xs text-neutral-600 font-normal">
|
|
232
|
+
{data?.lista ?? 'Sin lista'}
|
|
233
|
+
</span>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div className="flex flex-col w-auto gap-2">
|
|
237
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
238
|
+
Fecha estimada de inicio
|
|
239
|
+
</span>
|
|
240
|
+
<p className="text-xs text-neutral-600 font-normal">
|
|
241
|
+
{dateFormated(data?.f_inicio_estimada as Date)}
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
244
|
+
<div className="flex flex-col w-auto gap-2">
|
|
245
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
246
|
+
Fecha estimada de fin
|
|
247
|
+
</span>
|
|
248
|
+
<p className="text-xs text-neutral-600 font-normal">
|
|
249
|
+
{dateFormated(data?.f_fin_estimada as Date)}
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
<div className="flex flex-col w-auto gap-2">
|
|
253
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
254
|
+
Fecha de fin
|
|
255
|
+
</span>
|
|
256
|
+
<p className="text-xs text-neutral-600 font-normal">
|
|
257
|
+
{dateFormated(data?.ffin as Date)}
|
|
258
|
+
</p>
|
|
259
|
+
</div>
|
|
260
|
+
<div className="flex flex-col w-auto gap-2">
|
|
261
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
262
|
+
Creado
|
|
263
|
+
</span>
|
|
264
|
+
<p className="text-xs text-neutral-600 font-normal">
|
|
265
|
+
{dateFormated(data?.createdAt as Date)}
|
|
266
|
+
</p>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="flex flex-col w-auto gap-2">
|
|
269
|
+
<span className="text-sm text-neutral-900 font-medium">
|
|
270
|
+
Última modificación
|
|
271
|
+
</span>
|
|
272
|
+
<p className="text-xs text-neutral-600 font-normal">
|
|
273
|
+
{dateFormated(data?.updatedAt as Date)}
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</AccordionContent>
|
|
278
|
+
</AccordionItem>
|
|
279
|
+
</Accordion>
|
|
280
|
+
<TabDetailsTask items={items} />
|
|
281
|
+
</div>
|
|
282
|
+
</section>
|
|
283
|
+
</>
|
|
284
|
+
</>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { Button } from '@imj_media/imj-ui';
|
|
4
|
+
import { TabDetailsTaskProps } from '../../types';
|
|
5
|
+
|
|
6
|
+
export default function TabDetailsTask({ items }: TabDetailsTaskProps) {
|
|
7
|
+
const [selectedTab, setSelectedTab] = useState(1);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<div className="flex flex-row mb-2 border-b border-neutral-100 bg-white mt-4">
|
|
12
|
+
{items.map((item, index) => (
|
|
13
|
+
<div
|
|
14
|
+
key={item.id}
|
|
15
|
+
className={`border-r border-neutral-100 pr-3 py-1 ${
|
|
16
|
+
index !== 0 ? 'px-3' : ''
|
|
17
|
+
}`}
|
|
18
|
+
>
|
|
19
|
+
<Button
|
|
20
|
+
key={item.id}
|
|
21
|
+
variant="ghost"
|
|
22
|
+
onClick={() => setSelectedTab(item.id)}
|
|
23
|
+
className={`text-sm font-normal rounded-none py-1 h-8 ${
|
|
24
|
+
selectedTab === item.id
|
|
25
|
+
? 'text-primary border-b border-primary'
|
|
26
|
+
: 'text-neutral-400 border-b-0'
|
|
27
|
+
}`}
|
|
28
|
+
>
|
|
29
|
+
{item.label}
|
|
30
|
+
</Button>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
<div className="pt-4 flex-1 overflow-y-auto bg-white">
|
|
35
|
+
{items.find((item) => item.id === selectedTab)?.component}
|
|
36
|
+
</div>
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Checkbox from './Checkbox';
|
|
3
|
+
import ButtonAssignUsers from '../molecules/ButtonAssignUsers';
|
|
4
|
+
import { Icons, Tooltip } from '@imj_media/tasks-modules';
|
|
5
|
+
import { TasksKanbanGeneral } from '../../../infraestructure/interfaces/tasks-kanban-general';
|
|
6
|
+
import { AddOBPUrl, getMonthName } from '../../utils/formats';
|
|
7
|
+
import { COLORS } from '../../constants/colors';
|
|
8
|
+
import PriorityButton from '../tasks/PriorityButton';
|
|
9
|
+
import useElementPosition from '../../hooks/useElementPosition';
|
|
10
|
+
import ReactDOM from 'react-dom';
|
|
11
|
+
import DetailsTask from './DetailsTask';
|
|
12
|
+
|
|
13
|
+
const Task = ({
|
|
14
|
+
id,
|
|
15
|
+
task,
|
|
16
|
+
endDate,
|
|
17
|
+
users,
|
|
18
|
+
willBePaused,
|
|
19
|
+
nameProject,
|
|
20
|
+
idProject,
|
|
21
|
+
comments,
|
|
22
|
+
priority,
|
|
23
|
+
responsible,
|
|
24
|
+
nameRequiredTask,
|
|
25
|
+
statusRequiredTask,
|
|
26
|
+
status,
|
|
27
|
+
}: TasksKanbanGeneral) => {
|
|
28
|
+
const moreActionsRef = useRef<HTMLButtonElement>(null);
|
|
29
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
30
|
+
const { setElementPosition, position } = useElementPosition();
|
|
31
|
+
|
|
32
|
+
const MoreOptionsModal = ({ children }: { children: React.ReactNode }) => {
|
|
33
|
+
const [left, setLeft] = useState<number>();
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (moreActionsRef.current) {
|
|
37
|
+
setLeft(
|
|
38
|
+
(position?.x ?? 0) -
|
|
39
|
+
(moreActionsRef.current?.getBoundingClientRect()?.width ?? 0)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}, [moreActionsRef?.current, position?.x]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
position &&
|
|
46
|
+
ReactDOM.createPortal(
|
|
47
|
+
<div
|
|
48
|
+
className={`absolute w-fit h-fit ${!left ? 'hidden' : 'block'}`}
|
|
49
|
+
style={{ top: `${position?.y}px`, left: `${left}px` }}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</div>,
|
|
53
|
+
document.body
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const HeaderTask = () => (
|
|
59
|
+
<div className="flex justify-between items-center w-full h-fit relative">
|
|
60
|
+
<div className="flex items-center gap-[8px] w-[calc(100%-32px)] ">
|
|
61
|
+
<Checkbox
|
|
62
|
+
id={id}
|
|
63
|
+
nameRequiredTask={nameRequiredTask ?? ''}
|
|
64
|
+
statusRequiredTask={statusRequiredTask ?? 0}
|
|
65
|
+
status={status}
|
|
66
|
+
/>
|
|
67
|
+
<p className="title-text" onClick={() => setIsOpen(true)}>
|
|
68
|
+
{task}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
<button
|
|
72
|
+
ref={moreActionsRef}
|
|
73
|
+
onClick={() => {
|
|
74
|
+
setElementPosition(moreActionsRef as React.MutableRefObject<HTMLElement>);
|
|
75
|
+
}}
|
|
76
|
+
className="flex items-center justify-center text-sm font-extrabold bg-bg hover:filter-[drop-shadow(0_0_10px_#000000)] rounded-full w-[24px] h-[24px] m-block"
|
|
77
|
+
>
|
|
78
|
+
<span className="rotate-90">
|
|
79
|
+
<Icons icon="elipsis" size="xs" />
|
|
80
|
+
</span>
|
|
81
|
+
</button>
|
|
82
|
+
|
|
83
|
+
<MoreOptionsModal>
|
|
84
|
+
<button
|
|
85
|
+
className="w-max h-fit bg-bg-card rounded-lg p-l shadow-lg flex items-center gap-2 hover:bg-primary-light"
|
|
86
|
+
onClick={() => {
|
|
87
|
+
// TODO: Implement the functionality to move the task to working
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<p className="text-sm font-normal text-texts-placeholder font-regular">
|
|
91
|
+
Mover a trabajando
|
|
92
|
+
</p>
|
|
93
|
+
<Icons
|
|
94
|
+
icon="arrow_right"
|
|
95
|
+
size="xs"
|
|
96
|
+
strokeWidth={4}
|
|
97
|
+
color={COLORS.primary.regular}
|
|
98
|
+
/>
|
|
99
|
+
</button>
|
|
100
|
+
</MoreOptionsModal>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<div className="bg-bg-card border-cards max-w-[350px] rounded-lg relative h-fit p-[12px_12px] hover:border-active hover:bg-active cursor-pointer ">
|
|
107
|
+
<HeaderTask />
|
|
108
|
+
<div className="flex flex-col gap-[4px] py-2">
|
|
109
|
+
<p className="subtitle-text">{nameProject}</p>
|
|
110
|
+
{idProject && ( // TODO: Add nameMEDIUM
|
|
111
|
+
<p className="content-text">{idProject}</p>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
<div className="flex justify-between items-center w-full h-fit relative pt-5 pb-1">
|
|
115
|
+
<div className="flex items-center relative">
|
|
116
|
+
<ButtonAssignUsers
|
|
117
|
+
users={
|
|
118
|
+
users
|
|
119
|
+
.filter((user) => {
|
|
120
|
+
if (responsible) return user.id === responsible;
|
|
121
|
+
return true;
|
|
122
|
+
})
|
|
123
|
+
.map(AddOBPUrl) as any
|
|
124
|
+
}
|
|
125
|
+
onClick={() => {
|
|
126
|
+
// TODO: Implement the modal to assign users
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
{/* //!TODO: Add pausable */}
|
|
130
|
+
{willBePaused && (
|
|
131
|
+
<Tooltip
|
|
132
|
+
dispatch={
|
|
133
|
+
<button className="text-sm font-regular text-gray-400 flex items-center gap-2">
|
|
134
|
+
<Icons icon="pause" size="s" strokeWidth={2} />
|
|
135
|
+
<span>Pause</span>
|
|
136
|
+
</button>
|
|
137
|
+
}
|
|
138
|
+
>
|
|
139
|
+
<p className="text-sm font-regular text-white p-5">
|
|
140
|
+
Al pulsar el botón se solicitará razón del pausado
|
|
141
|
+
</p>
|
|
142
|
+
</Tooltip>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div className="flex items-center gap-2">
|
|
147
|
+
{/* //!TODO: add functionality to icons */}
|
|
148
|
+
<Icons icon="at" size="xs" />
|
|
149
|
+
<PriorityButton priority={priority} id={id} />
|
|
150
|
+
<Icons
|
|
151
|
+
icon="calendar"
|
|
152
|
+
size="xs"
|
|
153
|
+
strokeWidth={3}
|
|
154
|
+
color={COLORS.texts.subtext}
|
|
155
|
+
/>
|
|
156
|
+
<p className="text-sm font-regular text-texts-subtext">
|
|
157
|
+
{`${endDate.getDate()} ${getMonthName(endDate.getMonth()).slice(0, 3)}`}
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
{/* TODO: Add comments */}
|
|
162
|
+
{comments > 0 && (
|
|
163
|
+
<div className="border-t border-gray-200 mt-4 pt-2">
|
|
164
|
+
<button className="flex items-center gap-2">
|
|
165
|
+
<Icons icon="comment" size="xs" strokeWidth={4} />
|
|
166
|
+
<span className="text-sm font-regular text-gray-400">{comments}</span>
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
<DetailsTask taskId={id} isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export default Task;
|