@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,58 @@
1
+ export const filtersToShow = [
2
+ {
3
+ label: "Tipo de dependencia",
4
+ type: "select",
5
+ id: "dependency",
6
+ options: [{
7
+ id:"con",
8
+ name: "Con dependencia"
9
+ },{
10
+ id:"sin",
11
+ name: "Sin dependencia"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ label: "Por proyecto",
17
+ type: "select",
18
+ id: "project.id",
19
+ options: []
20
+ },
21
+ {
22
+ label: "Por responsable",
23
+ type: "select",
24
+ id: "owner.config",
25
+ options: [
26
+ {id:"yo", name:"Mis tareas"},
27
+ {id:"yo_y_mis_equipos", name:"Tareas mías y de mis equipos"},
28
+ {id:"mis_equipos", name:"Tareas de mis equipos"}
29
+ ]
30
+ },
31
+ {
32
+ label: "Por ejecutivo",
33
+ type: "select",
34
+ id: "salesman.name",
35
+ options: []
36
+ },
37
+ {
38
+ label: "Por dificultad",
39
+ type: "select",
40
+ id: "difficulty",
41
+ options: [{
42
+ id:0,
43
+ name: "Muy facil"
44
+ },{
45
+ id:1,
46
+ name: "Facil"
47
+ },{
48
+ id:2,
49
+ name: "Media"
50
+ },{
51
+ id:3,
52
+ name: "Dificil"
53
+ },{
54
+ id:4,
55
+ name: "Muy dificil"
56
+ }]
57
+ }
58
+ ]
@@ -0,0 +1,3 @@
1
+ export {default as ToDoBoard} from './ToDoBoard';
2
+ export {default as WorkingBoard} from './WorkingBoard';
3
+ export {default as DoneBoard} from './DoneBoard';
@@ -0,0 +1,50 @@
1
+ import { Icons } from "@imj_media/tasks-modules"
2
+ import { COLORS } from "../../constants/colors"
3
+ import ReactDOM from "react-dom";
4
+ import useElementPosition from "../../hooks/useElementPosition";
5
+ import { useEffect, useRef, useState } from "react";
6
+
7
+ const FilterButton = ({children, filterChildren}:{children:React.ReactNode, filterChildren:React.MutableRefObject<HTMLDivElement>}) => {
8
+ const {position, setElementPosition} = useElementPosition();
9
+ const ref = useRef<HTMLElement>(null);
10
+
11
+ const FiltersModal = ({children}:{children:React.ReactNode})=>{
12
+ const [left, setLeft] = useState<number>();
13
+
14
+ useEffect(()=>{
15
+ if(filterChildren.current){
16
+ setLeft((position?.x ?? 0 ) - (filterChildren.current?.getBoundingClientRect()?.width ?? 0));
17
+ }
18
+ },[filterChildren?.current, position?.x])
19
+
20
+ return position && ReactDOM.createPortal(
21
+ <div className={`absolute w-fit h-fit ${!left ? "hidden" :"block"}`} style={{top: `${position?.y}px`, left: `${left}px`}}>
22
+ {children}
23
+ </div>,
24
+ document.body
25
+ )
26
+ }
27
+
28
+ return (
29
+ <>
30
+ <label
31
+ ref={ref as React.LegacyRef<HTMLLabelElement>}
32
+ htmlFor="filter-button"
33
+ className="flex cursor-pointer items-center gap-m p-s text-texts-enfasis fill-texts-enfasis bg-bg rounded-lg px-l h-[30px] shadow-button-primary hover:shadow-button-primary-hover "
34
+ >
35
+ <Icons icon="filter" size="xs" strokeWidth={5} color={COLORS.primary.regular}/>
36
+ Filtrar
37
+ </label>
38
+ {/* this button is used to trigger the filter modal */}
39
+ <button className="hidden" id="filter-button" onClick={()=>{
40
+ setElementPosition(ref as React.MutableRefObject<HTMLElement>)
41
+ }}/>
42
+
43
+ <FiltersModal>
44
+ {children}
45
+ </FiltersModal>
46
+ </>
47
+ )
48
+ }
49
+
50
+ export default FilterButton
@@ -0,0 +1,70 @@
1
+ import { useFiltersLayoutContext, IFieldFilter } from '../../context/filtersLayout.context';
2
+ import { debounce } from '../../utils/inputs.functions';
3
+
4
+ const FilterContent = () => {
5
+ const {
6
+ fields,
7
+ updateFilters,
8
+ cleanFilters,
9
+ applyFilters,
10
+ initialFilters
11
+ } = useFiltersLayoutContext();
12
+
13
+ return (
14
+ <div className='flex flex-col gap-l min-w-[250px]'>
15
+ <div className='flex justify-between items-center'>
16
+ <p className='text-texts text-m'>Filtros</p>
17
+ <button className='text-xs font-semibold flex cursor-pointer items-center gap-m p-s text-texts-enfasis fill-texts-enfasis bg-bg rounded-lg px-l h-fit shadow-button-primary hover:shadow-button-primary-hover ' onClick={cleanFilters}>Limpiar</button>
18
+ </div>
19
+ <div className='flex flex-col gap-m'>
20
+ {fields?.map((field: IFieldFilter, index: number) => {
21
+ return (
22
+ <div key={index}>
23
+ <p className='text-sm'>{field.label}</p>
24
+ {field?.type === "select" ? (
25
+ <select
26
+ onChange={(e)=>
27
+ updateFilters({
28
+ name: e?.target?.name,
29
+ value: e?.target?.value
30
+ })
31
+ }
32
+ value={initialFilters?.[field?.id]}
33
+ className='w-full h-[30px] shadow-input rounded-lg p-s'
34
+ name={field.id}
35
+ id={field.id}
36
+ >
37
+ <option key={index} value={""}>Selecciona una opción</option>
38
+ {field?.options?.map((option: {id: string | number, name: string}, index: number)=>(
39
+ <option
40
+ key={index}
41
+ value={option.id}
42
+ >
43
+ {option.name}
44
+ </option>
45
+ ))}
46
+ </select>
47
+ ): (
48
+ <input
49
+ className='w-full h-[30px] shadow-input rounded-lg p-s'
50
+ type={field.type}
51
+ value={initialFilters?.[field?.id]}
52
+ name={field.label.toLowerCase().replace(/ /g, "_")}
53
+ onChange={debounce((e:React.ChangeEvent<HTMLInputElement>)=>updateFilters({
54
+ name: e?.target?.name,
55
+ value: e?.target?.value
56
+ }), 1000)}
57
+ />
58
+ )}
59
+
60
+
61
+ </div>
62
+ )
63
+ })}
64
+ </div>
65
+ <button className='btn-primary rounded-md py-s' onClick={applyFilters}>Filtrar</button>
66
+ </div>
67
+ )
68
+ }
69
+
70
+ export default FilterContent
@@ -0,0 +1,32 @@
1
+ import { InfoCampaniaProvider } from "../../context/kanbanCampania.context";
2
+ import { IComponents, IIndexComponents, IViews } from "../../types/layout.types";
3
+ import KanbanCampania from "../kanban-campania/KanbanCampania";
4
+ import KanbanGeneral from "../kanban-general/KanbanGeneral";
5
+ import ListaCampania from "../lista-campania/ListaCampania";
6
+
7
+ const IndexComponents = ({path, tab}: IIndexComponents) => {
8
+ const components: IComponents = {
9
+ "kanban-general": { "kanban": <KanbanGeneral/> },
10
+ "lista-campanias": {
11
+ "lista": <div>lista-campanias</div>,
12
+ "gantt": <div>gantt-campanias</div>
13
+ },
14
+ "kanban-campania": {
15
+ "kanban": <KanbanCampania />,
16
+ "lista": <ListaCampania />,
17
+ "gantt": <div>kanban-campania-gantt</div>
18
+ }
19
+ }
20
+
21
+ if(path === "kanban-campania"){
22
+ return (
23
+ <InfoCampaniaProvider project={1839}>
24
+ { (components[path] as IViews)[tab as keyof IViews] as React.ReactNode}
25
+ </InfoCampaniaProvider>
26
+ )
27
+ }
28
+
29
+ return (components[path] as IViews)[tab as keyof IViews] as React.ReactNode
30
+ }
31
+
32
+ export default IndexComponents
@@ -0,0 +1,22 @@
1
+ import { ITasksCampaign } from "../../../infraestructure/interfaces/tasks-campania"
2
+
3
+ interface ChildTaskProps extends ITasksCampaign {
4
+
5
+ }
6
+
7
+ const ChildTask = ({
8
+ id,
9
+ task,
10
+ priority,
11
+ startDate,
12
+ endDate,
13
+ status
14
+ }: ChildTaskProps) => {
15
+ return (
16
+ <div className="ml-[30px] mb-3 border-b border-gray-200 pb-3">
17
+ {task}
18
+ </div>
19
+ )
20
+ }
21
+
22
+ export default ChildTask
@@ -0,0 +1,30 @@
1
+ import { Icons } from "@imj_media/tasks-modules"
2
+ import { formatDate } from "../../utils/formats"
3
+ import { COLORS } from "../../constants/colors"
4
+
5
+ interface DateViewProps {
6
+ startDate: Date,
7
+ endDate: Date
8
+ }
9
+
10
+ const DateView = ({
11
+ startDate,
12
+ endDate
13
+ }: DateViewProps) => {
14
+
15
+ return (
16
+ <>
17
+ <div className="gap-s bg-gray-100 rounded-md h-[30px] w-fit flex justify-center items-center px-xxxl">
18
+ <Icons icon="calendar" size="xs" strokeWidth={4} color={COLORS.texts.subtext}/>
19
+ <p className="text-sm"> {formatDate(startDate)}</p>
20
+ </div>
21
+ <Icons icon="arrow_right" size="xs" strokeWidth={4} color={COLORS.texts.DEFAULT}/>
22
+ <div className="gap-s bg-gray-100 rounded-md h-[30px] w-fit flex justify-center items-center px-xxxl">
23
+ <Icons icon="calendar" size="xs" strokeWidth={4} color={COLORS.texts.subtext}/>
24
+ <p className="text-sm"> {formatDate(endDate)}</p>
25
+ </div>
26
+ </>
27
+ )
28
+ }
29
+
30
+ export default DateView
@@ -0,0 +1,21 @@
1
+ import { useInfoCampania } from "../../context/kanbanCampania.context";
2
+ import ParentTask from "./ParentTask";
3
+
4
+ const ListaCampania = () => {
5
+ const {tasksProject} = useInfoCampania();
6
+ let parentTasks = [...tasksProject?.data?.filter((task: any) => task.tasks.length > 0)];
7
+
8
+ return (
9
+ <div className="flex flex-col gap-m">
10
+ {parentTasks.map((task: any) => {
11
+ return (
12
+ <div className="flex flex-col gap-m border-b border-gray-200 pb-m">
13
+ <ParentTask id={task.id} />
14
+ </div>
15
+ )
16
+ })}
17
+ </div>
18
+ )
19
+ }
20
+
21
+ export default ListaCampania
@@ -0,0 +1,57 @@
1
+ import { Icons, Tooltip } from "@imj_media/tasks-modules";
2
+ import { COLORS } from "../../constants/colors";
3
+ import PriorityButton from "../tasks/PriorityButton";
4
+ import { useInfoCampania } from "../../context/kanbanCampania.context";
5
+ import DateView from "./Date";
6
+ import ChildTask from "./ChildTask";
7
+
8
+ const ParentTask = ({ id }: { id: number }) => {
9
+ const { tasksProject } = useInfoCampania();
10
+ const task = tasksProject?.data?.find((task: any) => task.id === id);
11
+
12
+ return (
13
+ <div className="flex flex-col gap-m ">
14
+ <div className="flex items-center gap-m flex-wrap hover:bg-gray-100">
15
+ <button className="-rotate-90">
16
+ <Icons icon="angle_down_outline" color={COLORS.texts.subtext}/>
17
+ </button>
18
+ <div className="flex items-center justify-center min-w-[20px] min-h-[20px] w-fit h-[20px] mx-1 bg-primary-medium rounded-full">
19
+ <p className="text-sm text-primary">
20
+ {task?.tasks?.length === 0 ? 1 : task?.tasks?.length}
21
+ </p>
22
+ </div>
23
+ <Tooltip
24
+ dispatch={
25
+ <p className="text-sm text-texts-subtext max-w-[400px] truncate">{task?.task}</p>
26
+ }
27
+ >
28
+ <p className="text-sm text-texts-subtext p-2 max-w-[400px]">{task?.task}</p>
29
+ </Tooltip>
30
+ <div className="gap-s bg-gray-100 rounded-md h-[30px] w-[30px] flex justify-center items-center">
31
+ <Icons icon="align_center" size="xs" strokeWidth={4} color={COLORS.texts.subtext}/>
32
+ </div>
33
+ <div className="gap-s bg-success-medium border border-success-pastel rounded-md h-[30px] w-[30px] flex justify-center items-center">
34
+ <PriorityButton id={id} priority={task?.priority}/>
35
+ </div>
36
+ <div className="gap-s bg-danger-light border border-danger-regular rounded-md h-[30px] w-[30px] flex justify-center items-center">
37
+ <Icons icon="at" size="xs" strokeWidth={4} color={COLORS.danger.regular}/>
38
+ </div>
39
+ <DateView startDate={task?.startDate} endDate={task?.endDate}/>
40
+ <div className="gap-s bg-gray-100 rounded-md h-[30px] flex justify-center items-center w-[30px]">
41
+ <Icons icon="trash" size="xs" strokeWidth={5} color={COLORS.texts.subtext}/>
42
+ </div>
43
+ </div>
44
+ <div className="ml-[30px]">
45
+ {task?.tasks?.length > 0 ? (
46
+ task?.tasks?.map((childTask: any) => (
47
+ <ParentTask id={childTask?.id} />
48
+ ))
49
+ ) : (
50
+ <ChildTask {...task}/>
51
+ )}
52
+ </div>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ export default ParentTask
@@ -0,0 +1,78 @@
1
+ import { CommentsProps } from '../../types';
2
+ import Comment from '../atoms/Comment';
3
+ import { getInitials, URL_API } from '../../utils/utils';
4
+ import { useForm } from 'react-hook-form';
5
+ import { useApis } from '../../context/useApis.context';
6
+ import { useState, useEffect } from 'react';
7
+
8
+ // @ts-ignore
9
+ import { Avatar, AvatarFallback, AvatarImage, Input } from '@imj_media/imj-ui';
10
+
11
+ export default function AllComments({ currentUser, data, taskId, onNewComment }: CommentsProps) {
12
+ const { tasks_api } = useApis();
13
+ const { register, handleSubmit, reset } = useForm();
14
+ const [comments, setComments] = useState<any[]>([]);
15
+
16
+ useEffect(() => {
17
+ if (data?.comentarios) {
18
+ const sortedComments = [...data.comentarios].sort(
19
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
20
+ );
21
+ setComments(sortedComments);
22
+ }
23
+ }, [data]);
24
+
25
+ const onSubmit = async (comment: any) => {
26
+ try {
27
+ const newComment = {
28
+ autor: {
29
+ id: currentUser?.id,
30
+ email: currentUser?.email,
31
+ nombre: currentUser?.nombre,
32
+ imagen: currentUser?.imagen,
33
+ },
34
+ comentario: comment.comment,
35
+ createdAt: new Date().toISOString(),
36
+ };
37
+
38
+ await tasks_api.post(`/api/comentarios`, {
39
+ data: {
40
+ autor: currentUser?.id,
41
+ comentario: comment.comment,
42
+ tarea: taskId,
43
+ },
44
+ });
45
+
46
+ onNewComment(true);
47
+ setComments((prevComments) => [newComment, ...prevComments]);
48
+ reset();
49
+ } catch (error) {
50
+ console.error('Error al agregar comentario:', error);
51
+ }
52
+ };
53
+
54
+ return (
55
+ <div className="flex flex-col h-full gap-4">
56
+ <form onSubmit={handleSubmit(onSubmit)} className="flex flex-row items-center gap-3">
57
+ <Avatar className="w-8 h-8">
58
+ <AvatarImage
59
+ src={`${URL_API}${currentUser?.imagen.formats.thumbnail?.url}`}
60
+ alt="@shadcn"
61
+ />
62
+ <AvatarFallback className="border border-x-primary-pastel">
63
+ {getInitials(currentUser?.nombre)}
64
+ </AvatarFallback>
65
+ </Avatar>
66
+ <Input
67
+ {...register('comment', { required: true })}
68
+ autoComplete="off"
69
+ placeholder="Escribe un comentario"
70
+ className="w-full bg-white border border-neutral-200"
71
+ />
72
+ </form>
73
+ {comments.map((comment, index) => (
74
+ <Comment key={index} comment={comment} />
75
+ ))}
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1,175 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import Avatar from '../atoms/Avatar';
3
+ import { Icons, Tooltip } from '@imj_media/tasks-modules';
4
+ import TooltipUser from '../atoms/TooltipUser';
5
+ import { IUsers } from '../../../infraestructure/interfaces/users';
6
+ import useElementPosition from '../../hooks/useElementPosition';
7
+ import ReactDOM from 'react-dom';
8
+ import InputSearch from '../atoms/InputSearch';
9
+ import useTeams from '../../hooks/useTeams';
10
+ import { ITeam } from '../../../infraestructure/interfaces/teams';
11
+ import useAllUsers from '../../hooks/useAllUsers';
12
+ import { AddOBPUrl } from '../../utils/formats';
13
+
14
+ const ButtonAssignUsers = ({
15
+ users,
16
+ onClick,
17
+ }: {
18
+ users: IUsers[] | [];
19
+ onClick: (option: any) => void;
20
+ }) => {
21
+ let buttonId = `button-assign-users-${Math.random().toString(36).substring(2, 15)}`;
22
+ const { setElementPosition, position, clearPosition } = useElementPosition();
23
+ const ref = useRef<HTMLParagraphElement>(null);
24
+
25
+ /**
26
+ * Get avatar more than three
27
+ * @returns a div with the number of users more than three
28
+ */
29
+ const getAvatarMoreThanThree = () => {
30
+ //TODO: Implement the logic to show the users in the hover
31
+ return (
32
+ users.length > 3 && (
33
+ <Tooltip
34
+ dispatch={
35
+ <div className="w-[28px] h-[28px] rounded-full btn-primary flex items-center justify-center border-2 border-white ml-[-8px]">
36
+ <p className="text-xs font-bold text-white ">{`+${
37
+ users.length - 3
38
+ }`}</p>
39
+ </div>
40
+ }
41
+ >
42
+ <div className="bg-containers w-fit h-fit p-2 rounded-lg border-containers">
43
+ {users?.slice(3).map((user, index) => (
44
+ <div key={index} className="flex items-center gap-2 ">
45
+ <Avatar key={index} imageUrl={user.image ?? ''} alt={user.name} />
46
+ <p className="text-xs font-bold content-text">{user.name}</p>
47
+ </div>
48
+ ))}
49
+ </div>
50
+ </Tooltip>
51
+ )
52
+ );
53
+ };
54
+
55
+ /**
56
+ * Map users
57
+ * @returns a div with the users
58
+ */
59
+ const mapUsers = () =>
60
+ users?.length > 0 &&
61
+ users.slice(0, 3).map((user, index) => (
62
+ <div className={`${index === 0 ? '' : 'ml-[-8px]'}`}>
63
+ <TooltipUser
64
+ trigger={<Avatar key={index} imageUrl={user.image} alt={user.name} />}
65
+ user={user}
66
+ showUser={!position?.x ? true : false}
67
+ />
68
+ </div>
69
+ ));
70
+
71
+ /**
72
+ * Label to active the button
73
+ * @returns a label with the children
74
+ */
75
+ const LabelToActiveTheButton = ({ children }: { children: React.ReactNode }) => (
76
+ <label ref={ref as any} htmlFor={buttonId} className="cursor-pointer flex items-center">
77
+ {children}
78
+ </label>
79
+ );
80
+
81
+ /**
82
+ * If the users length is 0, return a label with the icon
83
+ */
84
+ if (users.length === 0) {
85
+ return (
86
+ <LabelToActiveTheButton>
87
+ <Icons icon="user" size="xs" strokeWidth={3} />
88
+ </LabelToActiveTheButton>
89
+ );
90
+ }
91
+
92
+ const UsersModal = () => {
93
+ const [left, setLeft] = useState<number>();
94
+ const [selected, setSelected] = useState<'responsible' | 'team'>('team');
95
+ const { teams } = useTeams();
96
+ const { users: allUsers } = useAllUsers();
97
+
98
+ useEffect(() => {
99
+ if (ref.current) {
100
+ setLeft((position?.x ?? 0) - (ref.current?.getBoundingClientRect()?.width ?? 0));
101
+ }
102
+ }, [ref?.current, position?.x]);
103
+
104
+ const Tabs = () => {
105
+ const tabs = [
106
+ { id: 'team', label: 'Equipo' },
107
+ { id: 'responsible', label: 'Responsable' },
108
+ ];
109
+ return (
110
+ <div className="w-full flex justify-center gap-l text-texts-placeholder font-bold">
111
+ {tabs.map((tab, index) => (
112
+ <button
113
+ className={`w-full text-sm border-texts-placeholder text-center ${
114
+ selected === tab.id ? 'text-primary-pastel' : ''
115
+ } ${index === tabs.length - 1 ? 'border-r-0' : ' border-r-2'}`}
116
+ onClick={() => setSelected(tab.id as 'responsible' | 'team')}
117
+ >
118
+ {tab.label}
119
+ </button>
120
+ ))}
121
+ </div>
122
+ );
123
+ };
124
+
125
+ return (
126
+ position?.x &&
127
+ ReactDOM.createPortal(
128
+ <div
129
+ className={`absolute w-[300px] h-fit ${!left ? 'hidden' : 'block'}`}
130
+ style={{ top: `${position?.y}px`, left: `${left}px` }}
131
+ >
132
+ <div className="flex flex-col gap-m bg-bg-card border-cards rounded-lg p-2 max-h-[200px] overflow-y-hidden">
133
+ <Tabs />
134
+ <InputSearch
135
+ options={
136
+ selected === 'team'
137
+ ? teams.data.map((team: ITeam) => ({
138
+ id: team.id,
139
+ name: team.name,
140
+ image: '',
141
+ }))
142
+ : allUsers.data?.map(AddOBPUrl)
143
+ }
144
+ onselect={(option: any) => {
145
+ clearPosition();
146
+ if (onClick) onClick(option);
147
+ }}
148
+ />
149
+ </div>
150
+ </div>,
151
+ document.body
152
+ )
153
+ );
154
+ };
155
+ return (
156
+ <>
157
+ <LabelToActiveTheButton>
158
+ {mapUsers()}
159
+ {getAvatarMoreThanThree()}
160
+ </LabelToActiveTheButton>
161
+
162
+ {/* Hidden button to active the modal */}
163
+ <button
164
+ className="hidden"
165
+ id={buttonId}
166
+ onClick={() => setElementPosition(ref as React.MutableRefObject<HTMLElement>)}
167
+ />
168
+
169
+ {/* {getModalOfUsers()} */}
170
+ <UsersModal />
171
+ </>
172
+ );
173
+ };
174
+
175
+ export default ButtonAssignUsers;
@@ -0,0 +1,64 @@
1
+ import { format } from 'date-fns';
2
+ import { es } from 'date-fns/locale';
3
+ import { Avatar } from '../atoms';
4
+ // @ts-ignore
5
+ import { Label } from '@imj_media/imj-ui';
6
+
7
+ export default function DependentTasks({ data }: any) {
8
+ const statusMap: any = {
9
+ 0: {
10
+ color: 'bg-gray-400',
11
+ label: 'Sin estado',
12
+ },
13
+ 1: {
14
+ color: 'bg-primary-regular',
15
+ label: 'En curso',
16
+ },
17
+ 2: {
18
+ color: 'bg-success-regular',
19
+ label: 'Finalizada',
20
+ },
21
+ 3: {
22
+ color: 'bg-success-regular',
23
+ label: 'Finalizada',
24
+ },
25
+ };
26
+
27
+ const dateFormated = (date: any) => {
28
+ if (!date) return '';
29
+ return format(new Date(date), 'd MMM', { locale: es });
30
+ };
31
+ return (
32
+ <div>
33
+ <div className="flex flex-row items-center gap-4">
34
+ <Label className="text-lg font-medium text-neutral-900">Tareas dependientes</Label>
35
+ <span className="text-sm font-normal text-neutral-600 bg-[#E1EBF9] w-6 h-6 rounded-full flex items-center justify-center text-primary">
36
+ {data?.length}
37
+ </span>
38
+ </div>
39
+ <div className="flex flex-col gap-2 border border-neutral-200 rounded-lg mt-3">
40
+ {data?.map((task: any) => (
41
+ <div
42
+ key={task.id}
43
+ className="flex flex-row w-full items-center justify-between gap-s border-b border-neutral-200 py-1 px-2"
44
+ >
45
+ <div className="flex items-center gap-2">
46
+ <Avatar imageUrl={task.responsable?.image?.formats?.thumbnail?.url} />
47
+ <p className="text-sm font-normal w-[400px] text-nowrap text-ellipsis overflow-hidden">
48
+ {task?.texto_corto}
49
+ </p>
50
+ </div>
51
+ <div className="flex items-center gap-1">
52
+ <p className="text-sm font-normal">{dateFormated(task?.createdAt)}</p>
53
+ <div
54
+ className={`w-4 h-4 bg-gray-400 block rounded-full ${
55
+ statusMap[task?.estatus as any]?.color ?? 'bg-gray-400'
56
+ }`}
57
+ />
58
+ </div>
59
+ </div>
60
+ ))}
61
+ </div>
62
+ </div>
63
+ );
64
+ }