@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,79 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
import { Icons } from "@imj_media/tasks-modules"
|
|
4
|
+
import { COLORS } from "../../constants/colors";
|
|
5
|
+
import useFunctionsTasks from "../../hooks/useFunctionsTasks";
|
|
6
|
+
interface IPriorityButtonProps {
|
|
7
|
+
priority:number;
|
|
8
|
+
id:number;
|
|
9
|
+
}
|
|
10
|
+
const PriorityButton = ({priority, id}:IPriorityButtonProps) => {
|
|
11
|
+
const { updateTask } = useFunctionsTasks()
|
|
12
|
+
const buttonId = Math.random().toString(36);
|
|
13
|
+
const [position,setPosition] = useState<{x:number,y:number}|null>(null)
|
|
14
|
+
|
|
15
|
+
const getColor = (priority:number)=>{
|
|
16
|
+
const priorityColors = {
|
|
17
|
+
0:COLORS.primary.regular,
|
|
18
|
+
1:COLORS.success.regular,
|
|
19
|
+
2:COLORS.warning.regular,
|
|
20
|
+
3:COLORS.danger.regular
|
|
21
|
+
}[priority]
|
|
22
|
+
|
|
23
|
+
return priorityColors
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const OptionsPortal = ()=>{
|
|
27
|
+
if(!position) return null;
|
|
28
|
+
|
|
29
|
+
const OptionButton = ({priority,text}:{priority:number,text:string})=>{
|
|
30
|
+
return (
|
|
31
|
+
<button onClick={()=>{
|
|
32
|
+
updateTask({id, data: {prioridad:priority}, user: 272})
|
|
33
|
+
setPosition(null)
|
|
34
|
+
}} className="flex gap-m items-center px-l py-s hover:bg-primary-light">
|
|
35
|
+
<Icons icon="flag" size="xs" strokeWidth={3} color={getColor(priority)}/>
|
|
36
|
+
<p className="text-sm" style={{color:getColor(priority)}}>{text}</p>
|
|
37
|
+
</button>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return ReactDOM.createPortal(
|
|
42
|
+
<div onBlur={()=>{ setPosition(null) }} id={`${buttonId}-options`} style={{top: `${position.y + 5}px`, left: `${position.x + 5}px`}} className={`absolute h-fit p-m flex flex-col gap-2 bg-bg-card rounded-xl shadow-lg
|
|
43
|
+
w-max`}>
|
|
44
|
+
<OptionButton priority={0} text="Baja"/>
|
|
45
|
+
<OptionButton priority={1} text="Normal"/>
|
|
46
|
+
<OptionButton priority={2} text="Media"/>
|
|
47
|
+
<OptionButton priority={3} text="Alta"/>
|
|
48
|
+
</div>,
|
|
49
|
+
document.body
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="relative h-[22px]">
|
|
55
|
+
<label
|
|
56
|
+
onBlur={()=>{ setPosition(null) }}
|
|
57
|
+
htmlFor={buttonId}
|
|
58
|
+
className="flex items-center gap-2 cursor-pointer active:opacity-50"
|
|
59
|
+
>
|
|
60
|
+
<Icons icon="flag" size="xs" strokeWidth={3} color={getColor(priority)}/>
|
|
61
|
+
</label>
|
|
62
|
+
|
|
63
|
+
{/* Button to open the options */}
|
|
64
|
+
<button
|
|
65
|
+
id={buttonId}
|
|
66
|
+
onClick={(e)=>{
|
|
67
|
+
if(!position) setPosition({x:e.clientX,y:e.clientY})
|
|
68
|
+
else setPosition(null);
|
|
69
|
+
}}
|
|
70
|
+
className="hidden"
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
{/* Portal options */}
|
|
74
|
+
<OptionsPortal/>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default PriorityButton
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
//* components
|
|
3
|
+
import Tabs from "../molecules/Tabs"
|
|
4
|
+
import FilterButton from "../layout/FilterButton";
|
|
5
|
+
import FilterContent from "../layout/FilterContent";
|
|
6
|
+
import IndexComponents from "../layout/IndexComponents";
|
|
7
|
+
//* functions
|
|
8
|
+
import { debounce } from "../../utils/inputs.functions";
|
|
9
|
+
//* context
|
|
10
|
+
import { FiltersLayoutProvider } from "../../context/filtersLayout.context";
|
|
11
|
+
//* types
|
|
12
|
+
import { TPaths, TTab } from "../../types/layout.types";
|
|
13
|
+
|
|
14
|
+
const Layout = ({path}: { path: TPaths }) => {
|
|
15
|
+
const ComponentLayout = ()=>{
|
|
16
|
+
const [activeTab, setActiveTab] = useState<TTab>("kanban");
|
|
17
|
+
//TODO: add the filter by text
|
|
18
|
+
const [_filterByText, setFilterByText] = useState<string>("");
|
|
19
|
+
const filterRefs = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
//* set the active tab to kanban
|
|
22
|
+
useEffect( () => { setActiveTab("kanban") }, [ path ] )
|
|
23
|
+
|
|
24
|
+
//* get the title of the page
|
|
25
|
+
const getTitle = (path: TPaths) => {
|
|
26
|
+
return {
|
|
27
|
+
"kanban-general": "Kanban General",
|
|
28
|
+
"lista-campanias": "Campanias",
|
|
29
|
+
"kanban-campania": "Kanban de Campania"
|
|
30
|
+
}[path];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//* get the menus with tabs
|
|
34
|
+
const menusWithTabs = [ "lista-campanias", "kanban-campania" ];
|
|
35
|
+
|
|
36
|
+
//* get the tabs of the menu
|
|
37
|
+
const tabs: {[key:string]: TTab[]} = {
|
|
38
|
+
"lista-campanias": ["lista", "gantt"],
|
|
39
|
+
"kanban-campania": ["kanban", "lista", "gantt"]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="bg-bg-card p-m rounded-lg border border-border-card flex flex-col gap-m w-full h-full min-h-[100%] relative overflow-hidden">
|
|
44
|
+
<div className="w-full justify-between flex items-end border-b border-border-card pb-m px-l flex-wrap gap-l">
|
|
45
|
+
<div className="flex gap-xl">
|
|
46
|
+
<div> <h2 className="text-texts font-medium">{getTitle(path as any)}</h2> </div>
|
|
47
|
+
{menusWithTabs.includes(path) &&
|
|
48
|
+
<Tabs
|
|
49
|
+
tabs={tabs[path]}
|
|
50
|
+
renderItem={(e: TTab) => { setActiveTab(e) }}
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex items-center gap-s">
|
|
55
|
+
<input
|
|
56
|
+
onChange={debounce((e:React.ChangeEvent<HTMLInputElement>)=>setFilterByText(e?.target?.value), 1000)}
|
|
57
|
+
type="text"
|
|
58
|
+
placeholder="Buscar"
|
|
59
|
+
className="w-[200px] h-[30px] border-none bg-bg-card shadow-input border border-border-card rounded-lg px-l "
|
|
60
|
+
/>
|
|
61
|
+
<FilterButton filterChildren={filterRefs as React.MutableRefObject<HTMLDivElement>}>
|
|
62
|
+
<div ref={filterRefs} className="w-max max-w-[400px] h-fit bg-bg-card shadow-lg rounded-lg p-l">
|
|
63
|
+
<FilterContent />
|
|
64
|
+
</div>
|
|
65
|
+
</FilterButton>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="overflow-auto">
|
|
69
|
+
<IndexComponents path={path} tab={activeTab}/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
//* return the component with the layout filters context
|
|
77
|
+
return (
|
|
78
|
+
<FiltersLayoutProvider>
|
|
79
|
+
<ComponentLayout />
|
|
80
|
+
</FiltersLayoutProvider>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Layout;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
//* Primary
|
|
2
|
+
$primary_light: #E7E9F2;
|
|
3
|
+
$primary_dark: #334990;
|
|
4
|
+
$primary_medium: #DAE2EC;
|
|
5
|
+
$primary_regular: #4464C3;
|
|
6
|
+
|
|
7
|
+
//* ProgressBar
|
|
8
|
+
$orange_regular: #DC8921;
|
|
9
|
+
$blue_regular: #3658C1;
|
|
10
|
+
$green_regular: #039B59;
|
|
11
|
+
|
|
12
|
+
//* Silver
|
|
13
|
+
$silver_white: #FAFAFA;
|
|
14
|
+
$silver_light: #F5F5F5;
|
|
15
|
+
$silver_dark: #E9E9E9;
|
|
16
|
+
$silver_regular: #EFEFEF;
|
|
17
|
+
|
|
18
|
+
//* Gray
|
|
19
|
+
$gray_light: #D9D9D9;
|
|
20
|
+
$gray_regular: #D1D1D1;
|
|
21
|
+
$gray_dark: #C4C4C4;
|
|
22
|
+
|
|
23
|
+
//* Black
|
|
24
|
+
$black_light: #929292;
|
|
25
|
+
$black_medium: #4F4F4F;
|
|
26
|
+
$black_dark: #131313;
|
|
27
|
+
$black_regular: #252525;
|
|
28
|
+
|
|
29
|
+
$blue_medium: #E1EBF9;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
.table-container {
|
|
33
|
+
width: 100%;
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
padding: 1rem;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
border-radius: 2%;
|
|
38
|
+
background-color: $silver_white;
|
|
39
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
40
|
+
|
|
41
|
+
table {
|
|
42
|
+
width: 100%;
|
|
43
|
+
border-spacing: 0;
|
|
44
|
+
border: none;
|
|
45
|
+
background-color: $silver_white;
|
|
46
|
+
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
thead {
|
|
53
|
+
background-color: $silver_white;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
|
|
56
|
+
th {
|
|
57
|
+
padding: 16px;
|
|
58
|
+
text-align: left;
|
|
59
|
+
border-bottom: none; // Cambiado de "1px solid #e9ecef" a "none"
|
|
60
|
+
color: $gray_dark;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
font-size: 10px;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
letter-spacing: 0.5px;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: background-color 0.2s ease;
|
|
67
|
+
|
|
68
|
+
&:hover {
|
|
69
|
+
background-color: #e9ecef;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sort-icon {
|
|
73
|
+
margin-left: 8px;
|
|
74
|
+
opacity: 0.5;
|
|
75
|
+
|
|
76
|
+
&.active {
|
|
77
|
+
opacity: 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
tbody {
|
|
84
|
+
tr {
|
|
85
|
+
transition: background-color 0.2s ease;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
|
|
88
|
+
&:hover {
|
|
89
|
+
background-color: $primary_medium;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
td {
|
|
94
|
+
padding: 12px 16px;
|
|
95
|
+
text-align: left;
|
|
96
|
+
border-bottom: none; // Cambiado de "1px solid #e9ecef" a "none"
|
|
97
|
+
color: #212529;
|
|
98
|
+
font-size: 10px;
|
|
99
|
+
vertical-align: middle;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
.progress-container {
|
|
104
|
+
background-color: #e9ecef;
|
|
105
|
+
border-radius: 4px;
|
|
106
|
+
height: 8px;
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
|
|
109
|
+
.progress-bar {
|
|
110
|
+
height: 100%;
|
|
111
|
+
transition: width 0.3s ease;
|
|
112
|
+
|
|
113
|
+
&.green { background-color: $green_regular; }
|
|
114
|
+
&.blue { background-color: $blue_regular; }
|
|
115
|
+
&.orange { background-color: $orange_regular; }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.avatar-container {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 12px;
|
|
123
|
+
|
|
124
|
+
.avatar {
|
|
125
|
+
width: 28px;
|
|
126
|
+
height: 28px;
|
|
127
|
+
border-radius: 18px;
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
background-color: $primary_light;
|
|
132
|
+
color: dark;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
font-size: 10px!important;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.p-dropdown-sm {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
height: auto;
|
|
141
|
+
/* Elimina estilos como "position" o "z-index" si los hay */
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.dropdown-container {
|
|
145
|
+
position: relative;
|
|
146
|
+
display: inline-block;
|
|
147
|
+
width: 100%;
|
|
148
|
+
|
|
149
|
+
.dropdown-toggle {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: space-between;
|
|
153
|
+
width: 100%;
|
|
154
|
+
padding: 6px 12px;
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
background-color: transparent;
|
|
157
|
+
border: 1px solid $gray_light;
|
|
158
|
+
border-radius: 4px;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
|
|
161
|
+
&:hover {
|
|
162
|
+
background-color: $silver_light;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.dropdown-menu {
|
|
167
|
+
position: absolute;
|
|
168
|
+
top: 100%;
|
|
169
|
+
left: 0;
|
|
170
|
+
z-index: 1000;
|
|
171
|
+
width: 100%;
|
|
172
|
+
padding: 4px 0;
|
|
173
|
+
margin-top: 4px;
|
|
174
|
+
background-color: $silver_white;
|
|
175
|
+
border: 1px solid $gray_light;
|
|
176
|
+
border-radius: 4px;
|
|
177
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
178
|
+
|
|
179
|
+
.dropdown-item {
|
|
180
|
+
display: block;
|
|
181
|
+
width: 100%;
|
|
182
|
+
padding: 6px 12px;
|
|
183
|
+
font-size: 10px;
|
|
184
|
+
color: $black_regular;
|
|
185
|
+
text-align: left;
|
|
186
|
+
background-color: transparent;
|
|
187
|
+
border: none;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
|
|
190
|
+
&:hover {
|
|
191
|
+
background-color: $primary_light;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.pagination {
|
|
200
|
+
margin-top: 1rem;
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 8px;
|
|
205
|
+
padding: 1rem;
|
|
206
|
+
width: 100%;
|
|
207
|
+
height: fit-content;
|
|
208
|
+
background-color: $silver_white;
|
|
209
|
+
font-size: small;
|
|
210
|
+
|
|
211
|
+
button {
|
|
212
|
+
padding: 8px;
|
|
213
|
+
border: none;
|
|
214
|
+
background-color: $silver_white;
|
|
215
|
+
color: #495057;
|
|
216
|
+
border-radius: 50%;
|
|
217
|
+
cursor: pointer;
|
|
218
|
+
transition: all 0.2s ease;
|
|
219
|
+
width: auto;
|
|
220
|
+
height: auto;
|
|
221
|
+
align-items: center;
|
|
222
|
+
align-content: center;
|
|
223
|
+
text-align: center;
|
|
224
|
+
font-size: 7px;
|
|
225
|
+
|
|
226
|
+
&:disabled {
|
|
227
|
+
background-color: $silver_light;
|
|
228
|
+
color: $gray_dark;
|
|
229
|
+
cursor: not-allowed;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
&:hover:not(:disabled) {
|
|
233
|
+
background-color: $blue_medium;
|
|
234
|
+
border-color: $blue_medium;
|
|
235
|
+
color: $blue_regular;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
span {
|
|
240
|
+
font-size: 10px;
|
|
241
|
+
color: #6c757d;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
input[type="checkbox"] {
|
|
246
|
+
width: 16px;
|
|
247
|
+
height: 16px;
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
accent-color: #0d6efd;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.table-row {
|
|
253
|
+
transition: background-color 0.2s ease;
|
|
254
|
+
|
|
255
|
+
.table-row:nth-child(odd) {
|
|
256
|
+
background-color: #FAFAFA;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.table-row:nth-child(even) {
|
|
260
|
+
background-color: #F5F5F5;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&:hover {
|
|
264
|
+
background-color: $blue_medium !important; // Blue medium
|
|
265
|
+
color: white; // Para mejor contraste
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Avatar } from 'primereact/avatar';
|
|
3
|
+
import './TableList.scss';
|
|
4
|
+
import { Paginator } from 'primereact/paginator';
|
|
5
|
+
import { normalizeData } from '../../../../hooks/useNormalizedData';
|
|
6
|
+
import { Dropdown } from 'primereact/dropdown';
|
|
7
|
+
import { useApis } from '../../../../context/useApis.context';
|
|
8
|
+
|
|
9
|
+
const TableList = () => {
|
|
10
|
+
const [data, setData] = useState<any[]>([]);
|
|
11
|
+
const [first, setFirst] = useState(0);
|
|
12
|
+
const [rows, setRows] = useState(10); // Número inicial de filas por página
|
|
13
|
+
const [totalRecords, setTotalRecords] = useState(0);
|
|
14
|
+
const [orderBy, setOrderBy] = useState<string>('nombre'); // Estado para el orden
|
|
15
|
+
const { tasks_api } = useApis();
|
|
16
|
+
|
|
17
|
+
const fetchData = async (page: number, pageSize: number) => {
|
|
18
|
+
try {
|
|
19
|
+
const response = await tasks_api.get('/listado_campanias', {
|
|
20
|
+
params: {
|
|
21
|
+
'pagination[page]': page,
|
|
22
|
+
'pagination[pageSize]': pageSize,
|
|
23
|
+
orderBy: orderBy, // Agregar orderBy a los parámetros
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const normalizedData = normalizeData(response.data);
|
|
27
|
+
setData(normalizedData);
|
|
28
|
+
setTotalRecords(response.data.meta.total);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error fetching data:', error);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const page = Math.floor(first / rows) + 1;
|
|
36
|
+
fetchData(page, rows);
|
|
37
|
+
}, [first, rows, orderBy]); // Se ejecutará cuando cambien first, rows o orderBy
|
|
38
|
+
|
|
39
|
+
const onPageChange = (event: { first: number; rows: number }) => {
|
|
40
|
+
setFirst(event.first);
|
|
41
|
+
setRows(event.rows);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const rowsPerPageOptions = [
|
|
45
|
+
{ label: '5', value: 5 },
|
|
46
|
+
{ label: '10', value: 10 },
|
|
47
|
+
{ label: '15', value: 15 },
|
|
48
|
+
{ label: '20', value: 20 },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const onRowsPerPageChange = (e: { value: number }) => {
|
|
52
|
+
if (e.value !== rows) {
|
|
53
|
+
setRows(e.value);
|
|
54
|
+
setFirst(0); // Reinicia la paginación para empezar desde el inicio
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleOrderByChange = (column: string) => {
|
|
59
|
+
setOrderBy((prevOrderBy) => (prevOrderBy === column ? `${column}:desc` : column));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getSortIcon = (column: string) => {
|
|
63
|
+
if (orderBy === column) return '↑↓'; // Ascendente
|
|
64
|
+
if (orderBy === `${column}:desc`) return '↓↑'; // Descendente
|
|
65
|
+
return '↑↓'; // Sin ordenar
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={'table-container'}>
|
|
70
|
+
<table
|
|
71
|
+
className={'table'}
|
|
72
|
+
style={{
|
|
73
|
+
borderCollapse: 'collapse',
|
|
74
|
+
width: '100%',
|
|
75
|
+
borderSpacing: '0',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<thead>
|
|
79
|
+
<tr>
|
|
80
|
+
<th onClick={() => handleOrderByChange('nombre')}>
|
|
81
|
+
<label style={{ color: '#C4C4C4', fontSize: '10px' }}>
|
|
82
|
+
{getSortIcon('nombre')}
|
|
83
|
+
</label>
|
|
84
|
+
Campaña
|
|
85
|
+
</th>
|
|
86
|
+
<th onClick={() => handleOrderByChange('inicia')}>
|
|
87
|
+
<label style={{ color: '#C4C4C4', fontSize: '10px' }}>
|
|
88
|
+
{getSortIcon('inicia')}
|
|
89
|
+
</label>
|
|
90
|
+
Fecha Inicio
|
|
91
|
+
</th>
|
|
92
|
+
<th onClick={() => handleOrderByChange('termina')}>
|
|
93
|
+
<label style={{ color: '#C4C4C4', fontSize: '10px' }}>
|
|
94
|
+
{getSortIcon('termina')}
|
|
95
|
+
</label>
|
|
96
|
+
Fecha Fin
|
|
97
|
+
</th>
|
|
98
|
+
<th>Status</th>
|
|
99
|
+
<th>Responsable</th>
|
|
100
|
+
</tr>
|
|
101
|
+
</thead>
|
|
102
|
+
<tbody>
|
|
103
|
+
{data?.map((item, index) => (
|
|
104
|
+
<tr key={index} className="table-row">
|
|
105
|
+
<td>{item?.campania?.nombre}</td>
|
|
106
|
+
<td>{item?.campania?.inicia}</td>
|
|
107
|
+
<td>{item?.campania?.termina}</td>
|
|
108
|
+
<td style={{ width: '20%' }}>
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
display: 'flex',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
gap: '15px',
|
|
114
|
+
padding: 'none',
|
|
115
|
+
fontSize: '12px',
|
|
116
|
+
width: 'auto',
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<label
|
|
120
|
+
style={{
|
|
121
|
+
width: '20px',
|
|
122
|
+
minWidth: '50px',
|
|
123
|
+
textAlign: 'right',
|
|
124
|
+
color: '#000',
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{item?.progreso}%
|
|
128
|
+
</label>
|
|
129
|
+
<div
|
|
130
|
+
style={{
|
|
131
|
+
position: 'relative',
|
|
132
|
+
height: '10px',
|
|
133
|
+
flex: 1,
|
|
134
|
+
backgroundColor: '#C4C4C4',
|
|
135
|
+
borderRadius: '10px',
|
|
136
|
+
overflow: 'hidden',
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
position: 'absolute',
|
|
142
|
+
top: 0,
|
|
143
|
+
left: 0,
|
|
144
|
+
height: '100%',
|
|
145
|
+
width: `${item.progreso}%`,
|
|
146
|
+
backgroundColor:
|
|
147
|
+
item.progreso === 100
|
|
148
|
+
? '#039B59'
|
|
149
|
+
: item.progreso > 50
|
|
150
|
+
? '#3658C1'
|
|
151
|
+
: item.progreso > 0
|
|
152
|
+
? '#DC8921'
|
|
153
|
+
: '#C4C4C4',
|
|
154
|
+
borderRadius: '10px',
|
|
155
|
+
transition: 'width 0.3s ease-in-out',
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</td>
|
|
161
|
+
<td style={{ width: 'auto' }}>
|
|
162
|
+
<div className="avatar-container">
|
|
163
|
+
<Avatar
|
|
164
|
+
className="avatar"
|
|
165
|
+
label={item?.campania?.responsable?.nombre?.charAt(0)}
|
|
166
|
+
shape="circle"
|
|
167
|
+
/>
|
|
168
|
+
<span style={{ marginLeft: '8px', fontSize: '12px' }}>
|
|
169
|
+
{item?.campania?.responsable?.nombre}
|
|
170
|
+
</span>
|
|
171
|
+
</div>
|
|
172
|
+
</td>
|
|
173
|
+
</tr>
|
|
174
|
+
))}
|
|
175
|
+
</tbody>
|
|
176
|
+
</table>
|
|
177
|
+
<div
|
|
178
|
+
className="pagination"
|
|
179
|
+
style={{
|
|
180
|
+
background: '#FAFAFA',
|
|
181
|
+
marginTop: '1rem',
|
|
182
|
+
display: 'flex',
|
|
183
|
+
gap: '0.5rem',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<span
|
|
187
|
+
style={{
|
|
188
|
+
alignItems: 'center',
|
|
189
|
+
fontSize: '10px',
|
|
190
|
+
textAlign: 'left',
|
|
191
|
+
width: 'auto',
|
|
192
|
+
height: 'auto',
|
|
193
|
+
padding: '12px',
|
|
194
|
+
whiteSpace: 'nowrap',
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
{rows} elementos de {totalRecords}
|
|
198
|
+
</span>
|
|
199
|
+
|
|
200
|
+
<Paginator
|
|
201
|
+
first={first}
|
|
202
|
+
rows={rows}
|
|
203
|
+
totalRecords={totalRecords}
|
|
204
|
+
rowsPerPageOptions={[5, 10, 20, 50]}
|
|
205
|
+
onPageChange={onPageChange}
|
|
206
|
+
className="pagination"
|
|
207
|
+
template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink"
|
|
208
|
+
/>
|
|
209
|
+
<div
|
|
210
|
+
style={{
|
|
211
|
+
display: 'flex',
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
gap: '8px',
|
|
214
|
+
whiteSpace: 'nowrap',
|
|
215
|
+
padding: '0 1rem',
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
<span style={{ fontSize: '12px' }}>Filas por página:</span>
|
|
219
|
+
<Dropdown
|
|
220
|
+
value={rows}
|
|
221
|
+
options={rowsPerPageOptions}
|
|
222
|
+
onChange={onRowsPerPageChange}
|
|
223
|
+
style={{
|
|
224
|
+
minWidth: '5rem',
|
|
225
|
+
width: 'auto',
|
|
226
|
+
height: '2rem',
|
|
227
|
+
zIndex: '999999',
|
|
228
|
+
transform: 'translateY(-100%)', // Move dropdown upwards
|
|
229
|
+
}}
|
|
230
|
+
className="p-dropdown-sm"
|
|
231
|
+
optionLabel="label"
|
|
232
|
+
optionValue="value"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
export default TableList;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TableList } from './TableList';
|