@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.
Files changed (114) hide show
  1. package/.env.template +8 -0
  2. package/.storybook/main.ts +26 -0
  3. package/.storybook/preview.ts +15 -0
  4. package/LICENSE.md +21 -0
  5. package/README.md +50 -0
  6. package/core/actions/get_all_users.action.ts +16 -0
  7. package/core/actions/get_salesman_response.action.ts +16 -0
  8. package/core/actions/get_tasks_project.action.ts +62 -0
  9. package/core/actions/get_tasks_response.action.ts +56 -0
  10. package/eslint.config.js +28 -0
  11. package/global.d.ts +3 -0
  12. package/infraestructure/interfaces/salesmans-obp-response.ts +43 -0
  13. package/infraestructure/interfaces/salesmans-obp.ts +4 -0
  14. package/infraestructure/interfaces/tasks-campania-response.ts +118 -0
  15. package/infraestructure/interfaces/tasks-campania.ts +26 -0
  16. package/infraestructure/interfaces/tasks-kanban-general.ts +25 -0
  17. package/infraestructure/interfaces/tasks-reponse.ts +111 -0
  18. package/infraestructure/interfaces/teams-response.ts +7 -0
  19. package/infraestructure/interfaces/teams.ts +5 -0
  20. package/infraestructure/interfaces/users-obp-response.ts +52 -0
  21. package/infraestructure/interfaces/users.ts +5 -0
  22. package/infraestructure/mappers/all-users-obp.ts +12 -0
  23. package/infraestructure/mappers/campaign-tasks.ts +35 -0
  24. package/infraestructure/mappers/kanban-tasks.ts +35 -0
  25. package/infraestructure/mappers/salesmans-obp.ts +11 -0
  26. package/infraestructure/mappers/teams.ts +12 -0
  27. package/package.json +61 -0
  28. package/postcss.config.js +6 -0
  29. package/src/components/atoms/Avatar.tsx +14 -0
  30. package/src/components/atoms/Comment.tsx +33 -0
  31. package/src/components/atoms/InputSearch.tsx +40 -0
  32. package/src/components/atoms/SkeletonCard.tsx +17 -0
  33. package/src/components/atoms/TabButton.tsx +16 -0
  34. package/src/components/atoms/TooltipUser.tsx +26 -0
  35. package/src/components/atoms/index.ts +2 -0
  36. package/src/components/index.ts +3 -0
  37. package/src/components/kanban-campania/DoneBoard.tsx +12 -0
  38. package/src/components/kanban-campania/KanbanCampania.tsx +39 -0
  39. package/src/components/kanban-campania/ToDoBoard.tsx +12 -0
  40. package/src/components/kanban-campania/WorkingBoard.tsx +13 -0
  41. package/src/components/kanban-campania/filters.ts +46 -0
  42. package/src/components/kanban-campania/index.ts +3 -0
  43. package/src/components/kanban-general/DoneBoard.tsx +12 -0
  44. package/src/components/kanban-general/KanbanGeneral.tsx +40 -0
  45. package/src/components/kanban-general/ToDoBoard.tsx +12 -0
  46. package/src/components/kanban-general/WorkingBoard.tsx +13 -0
  47. package/src/components/kanban-general/filters.ts +58 -0
  48. package/src/components/kanban-general/index.ts +3 -0
  49. package/src/components/layout/FilterButton.tsx +50 -0
  50. package/src/components/layout/FilterContent.tsx +70 -0
  51. package/src/components/layout/IndexComponents.tsx +32 -0
  52. package/src/components/lista-campania/ChildTask.tsx +22 -0
  53. package/src/components/lista-campania/Date.tsx +30 -0
  54. package/src/components/lista-campania/ListaCampania.tsx +21 -0
  55. package/src/components/lista-campania/ParentTask.tsx +57 -0
  56. package/src/components/molecules/AllComments.tsx +78 -0
  57. package/src/components/molecules/ButtonAssignUsers.tsx +175 -0
  58. package/src/components/molecules/DependentTasks.tsx +64 -0
  59. package/src/components/molecules/Tabs.tsx +39 -0
  60. package/src/components/molecules/index.ts +1 -0
  61. package/src/components/organisms/Board.tsx +87 -0
  62. package/src/components/organisms/Checkbox.tsx +45 -0
  63. package/src/components/organisms/DetailsTask.tsx +286 -0
  64. package/src/components/organisms/TabDetailsTask.tsx +39 -0
  65. package/src/components/organisms/Task.tsx +176 -0
  66. package/src/components/organisms/index.ts +2 -0
  67. package/src/components/tasks/PriorityButton.tsx +79 -0
  68. package/src/components/templates/Layout.tsx +84 -0
  69. package/src/components/templates/TableList/components/TableList.scss +270 -0
  70. package/src/components/templates/TableList/components/TableList.tsx +239 -0
  71. package/src/components/templates/TableList/components/index.tsx +1 -0
  72. package/src/constants/colors.ts +64 -0
  73. package/src/constants/gaps.ts +8 -0
  74. package/src/constants/paddings.ts +8 -0
  75. package/src/constants/shadows.ts +5 -0
  76. package/src/context/filtersLayout.context.tsx +118 -0
  77. package/src/context/kanbanCampania.context.tsx +50 -0
  78. package/src/context/useApis.context.tsx +47 -0
  79. package/src/context/userLog.context.tsx +50 -0
  80. package/src/env.d.ts +10 -0
  81. package/src/functions/taskCalculations.tsx +818 -0
  82. package/src/hooks/useAllUsers.ts +18 -0
  83. package/src/hooks/useCheckTask.tsx +15 -0
  84. package/src/hooks/useComerciales.ts +58 -0
  85. package/src/hooks/useDoneTasks.ts +90 -0
  86. package/src/hooks/useElementPosition.ts +34 -0
  87. package/src/hooks/useFunctionsTasks.ts +57 -0
  88. package/src/hooks/useNormalizedData.ts +36 -0
  89. package/src/hooks/useTeams.ts +19 -0
  90. package/src/hooks/useToDoTasks.ts +89 -0
  91. package/src/hooks/useWorkingTasks.ts +85 -0
  92. package/src/index.css +55 -0
  93. package/src/index.ts +2 -0
  94. package/src/index.tsx +1 -0
  95. package/src/pages/App.tsx +42 -0
  96. package/src/pages/NoAccessToken.tsx +20 -0
  97. package/src/pages/NoUser.tsx +20 -0
  98. package/src/stories/AppTasks.stories.tsx +36 -0
  99. package/src/stories/Table.stories.tsx +160 -0
  100. package/src/types/index.ts +107 -0
  101. package/src/types/interfaces.ts +67 -0
  102. package/src/types/layout.types.ts +30 -0
  103. package/src/utils/filters.functions.ts +17 -0
  104. package/src/utils/formats.ts +33 -0
  105. package/src/utils/functionsStorybook.tsx +0 -0
  106. package/src/utils/inputs.functions.ts +25 -0
  107. package/src/utils/tanstack.functions.ts +19 -0
  108. package/src/utils/utils.ts +12 -0
  109. package/src/vite-env.d.ts +1 -0
  110. package/tailwind.config.js +31 -0
  111. package/tsconfig.app.json +26 -0
  112. package/tsconfig.json +7 -0
  113. package/tsconfig.node.json +24 -0
  114. 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;
@@ -0,0 +1,2 @@
1
+ export {default as Board} from "./Board";
2
+ export {default as Task} from "./Task";