@orbe-agro/client-core 5.6.187 → 5.6.192
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/dist/@ecme/assets/styles/app.css +17 -0
- package/dist/@ecme/assets/styles/tailwind/index.css +17 -0
- package/dist/@ecme/components/layouts/PostLoginLayout/components/CollapsibleSide.stories.js.map +1 -1
- package/dist/@ecme/components/layouts/PostLoginLayout/components/ContentOverlay.stories.js.map +1 -1
- package/dist/@ecme/components/layouts/PostLoginLayout/components/FrameLessSide.stories.js.map +1 -1
- package/dist/@ecme/components/layouts/PostLoginLayout/components/StackedSide.stories.js.map +1 -1
- package/dist/@ecme/components/layouts/PostLoginLayout/components/TopBarClassic.stories.js.map +1 -1
- package/dist/@ecme/components/template/Footer.js +1 -1
- package/dist/@ecme/components/template/Header.stories.js.map +1 -1
- package/dist/@ecme/components/template/Search.js +276 -72
- package/dist/@ecme/components/template/Search.js.map +1 -1
- package/dist/@ecme/components/template/Search.stories.js +18 -2
- package/dist/@ecme/components/template/Search.stories.js.map +1 -1
- package/dist/@ecme/index.js +0 -3
- package/dist/@ecme/index.js.map +1 -1
- package/dist/@ecme/locales/index.js +1 -4
- package/dist/@ecme/locales/index.js.map +1 -1
- package/dist/@ecme/mock/data/favoritosMenuData.js +5 -0
- package/dist/@ecme/mock/data/favoritosMenuData.js.map +1 -0
- package/dist/@ecme/mock/fakeApi/favoritosMenuFakeApi.js +64 -0
- package/dist/@ecme/mock/fakeApi/favoritosMenuFakeApi.js.map +1 -0
- package/dist/@ecme/mock/index.js +1 -0
- package/dist/@ecme/mock/index.js.map +1 -1
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/CollapsibleSide.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/CollapsibleSide.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/ContentOverlay.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/ContentOverlay.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/FrameLessSide.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/FrameLessSide.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/StackedSide.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/StackedSide.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/TopBarClassic.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/components/TopBarClassic.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/template/Header.stories.d.ts +7 -0
- package/dist/@types/@ecme/components/template/Header.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/components/template/Search.d.ts.map +1 -1
- package/dist/@types/@ecme/components/template/Search.stories.d.ts +9 -0
- package/dist/@types/@ecme/components/template/Search.stories.d.ts.map +1 -0
- package/dist/@types/@ecme/locales/index.d.ts +0 -1
- package/dist/@types/@ecme/locales/index.d.ts.map +1 -1
- package/dist/@types/@ecme/mock/data/favoritosMenuData.d.ts +3 -0
- package/dist/@types/@ecme/mock/data/favoritosMenuData.d.ts.map +1 -0
- package/dist/@types/@ecme/mock/fakeApi/favoritosMenuFakeApi.d.ts +2 -0
- package/dist/@types/@ecme/mock/fakeApi/favoritosMenuFakeApi.d.ts.map +1 -0
- package/dist/@types/@ecme/mock/index.d.ts +1 -0
- package/dist/@types/@ecme/mock/index.d.ts.map +1 -1
- package/dist/@types/base/@types/api/request/dm/TCreateFavoritoMenuRequest.d.ts +4 -0
- package/dist/@types/base/@types/api/request/dm/TCreateFavoritoMenuRequest.d.ts.map +1 -0
- package/dist/@types/base/@types/api/request/dm/TReorderFavoritosMenuRequest.d.ts +4 -0
- package/dist/@types/base/@types/api/request/dm/TReorderFavoritosMenuRequest.d.ts.map +1 -0
- package/dist/@types/base/@types/api/response/dadosMestres/TFavoritoMenuResponse.d.ts +8 -0
- package/dist/@types/base/@types/api/response/dadosMestres/TFavoritoMenuResponse.d.ts.map +1 -0
- package/dist/@types/base/@types/models/performance/ppr.d.ts +48 -0
- package/dist/@types/base/@types/models/performance/ppr.d.ts.map +1 -1
- package/dist/@types/base/configs/endpoints.config/DadosMestres/FavoritoMenu/favoritoMenu.endpoint.config.d.ts +8 -0
- package/dist/@types/base/configs/endpoints.config/DadosMestres/FavoritoMenu/favoritoMenu.endpoint.config.d.ts.map +1 -0
- package/dist/@types/base/configs/endpoints.config/endpoints.navigation.d.ts +7 -0
- package/dist/@types/base/configs/endpoints.config/endpoints.navigation.d.ts.map +1 -1
- package/dist/@types/base/configs/endpoints.config/performance/config/ppr.d.ts +1 -0
- package/dist/@types/base/configs/endpoints.config/performance/config/ppr.d.ts.map +1 -1
- package/dist/@types/base/configs/endpoints.config/performance/performance.d.ts +1 -0
- package/dist/@types/base/configs/endpoints.config/performance/performance.d.ts.map +1 -1
- package/dist/@types/base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService.d.ts +8 -0
- package/dist/@types/base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService.d.ts.map +1 -0
- package/dist/@types/base/services/modules/performance/PerformanceService.d.ts +3 -1
- package/dist/@types/base/services/modules/performance/PerformanceService.d.ts.map +1 -1
- package/dist/@types/base/utils/functions/stringUtils.d.ts.map +1 -1
- package/dist/base/@types/api/request/dm/TCreateFavoritoMenuRequest.js +2 -0
- package/dist/base/@types/api/request/dm/TCreateFavoritoMenuRequest.js.map +1 -0
- package/dist/base/@types/api/request/dm/TReorderFavoritosMenuRequest.js +2 -0
- package/dist/base/@types/api/request/dm/TReorderFavoritosMenuRequest.js.map +1 -0
- package/dist/base/@types/api/response/dadosMestres/TFavoritoMenuResponse.js +2 -0
- package/dist/base/@types/api/response/dadosMestres/TFavoritoMenuResponse.js.map +1 -0
- package/dist/base/assets/styles/init.css +18 -0
- package/dist/base/configs/api.config.js.map +1 -1
- package/dist/base/configs/endpoints.config/DadosMestres/FavoritoMenu/favoritoMenu.endpoint.config.js +11 -0
- package/dist/base/configs/endpoints.config/DadosMestres/FavoritoMenu/favoritoMenu.endpoint.config.js.map +1 -0
- package/dist/base/configs/endpoints.config/endpoints.navigation.js +2 -0
- package/dist/base/configs/endpoints.config/endpoints.navigation.js.map +1 -1
- package/dist/base/configs/endpoints.config/performance/config/ppr.js +2 -1
- package/dist/base/configs/endpoints.config/performance/config/ppr.js.map +1 -1
- package/dist/base/configs/navigation.config/submenu/performance/config/exportarResultadosPpr.submenu.js +1 -1
- package/dist/base/configs/navigation.config/submenu/performance/config/exportarResultadosPpr.submenu.js.map +1 -1
- package/dist/base/index.js +3 -1
- package/dist/base/services/index.js +3 -1
- package/dist/base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService.js +35 -0
- package/dist/base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService.js.map +1 -0
- package/dist/base/services/modules/index.js +3 -1
- package/dist/base/services/modules/performance/PerformanceService.js +15 -0
- package/dist/base/services/modules/performance/PerformanceService.js.map +1 -1
- package/dist/base/services/modules/performance/index.js +3 -1
- package/dist/base/utils/functions/stringUtils.js +2 -0
- package/dist/base/utils/functions/stringUtils.js.map +1 -1
- package/lib/@ecme/components/layouts/PostLoginLayout/components/CollapsibleSide.stories.tsx +2 -2
- package/lib/@ecme/components/layouts/PostLoginLayout/components/ContentOverlay.stories.tsx +2 -2
- package/lib/@ecme/components/layouts/PostLoginLayout/components/FrameLessSide.stories.tsx +2 -2
- package/lib/@ecme/components/layouts/PostLoginLayout/components/StackedSide.stories.tsx +2 -2
- package/lib/@ecme/components/layouts/PostLoginLayout/components/TopBarClassic.stories.tsx +2 -2
- package/lib/@ecme/components/template/Header.stories.tsx +2 -2
- package/lib/@ecme/components/template/Search.stories.tsx +23 -5
- package/lib/@ecme/components/template/Search.tsx +366 -83
- package/lib/@ecme/locales/index.ts +0 -4
- package/lib/@ecme/mock/data/favoritosMenuData.ts +3 -0
- package/lib/@ecme/mock/fakeApi/favoritosMenuFakeApi.ts +87 -0
- package/lib/@ecme/mock/index.ts +1 -0
- package/lib/base/@types/api/request/dm/TCreateFavoritoMenuRequest.ts +3 -0
- package/lib/base/@types/api/request/dm/TReorderFavoritosMenuRequest.ts +3 -0
- package/lib/base/@types/api/response/dadosMestres/TFavoritoMenuResponse.ts +7 -0
- package/lib/base/@types/models/performance/ppr.ts +67 -0
- package/lib/base/configs/api.config.ts +2 -2
- package/lib/base/configs/endpoints.config/DadosMestres/FavoritoMenu/favoritoMenu.endpoint.config.ts +10 -0
- package/lib/base/configs/endpoints.config/endpoints.navigation.ts +2 -0
- package/lib/base/configs/endpoints.config/performance/config/ppr.ts +1 -0
- package/lib/base/configs/navigation.config/submenu/performance/config/exportarResultadosPpr.submenu.ts +1 -1
- package/lib/base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService.ts +35 -0
- package/lib/base/services/modules/performance/PerformanceService.ts +16 -0
- package/lib/base/utils/functions/stringUtils.ts +2 -0
- package/package.json +1 -1
|
@@ -11,10 +11,24 @@ import debounce from 'lodash/debounce'
|
|
|
11
11
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
12
12
|
import Highlighter from 'react-highlight-words'
|
|
13
13
|
import { useTranslation } from 'react-i18next'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
HiChevronRight,
|
|
16
|
+
HiOutlineSearch,
|
|
17
|
+
HiOutlineStar,
|
|
18
|
+
HiStar,
|
|
19
|
+
HiOutlineTrash,
|
|
20
|
+
} from 'react-icons/hi'
|
|
15
21
|
import { PiMagnifyingGlassDuotone } from 'react-icons/pi'
|
|
16
22
|
import { Link } from 'react-router-dom'
|
|
17
23
|
import { Spinner } from '../ui'
|
|
24
|
+
import Notification from '@/components/ui/Notification'
|
|
25
|
+
import toast from '@/components/ui/toast'
|
|
26
|
+
import {
|
|
27
|
+
apiFindFavoritosByUsuario,
|
|
28
|
+
apiDeleteFavoritoMenu,
|
|
29
|
+
apiCreateFavoritoMenu,
|
|
30
|
+
} from '@base/services/modules/DadosMestres/FavoritoMenu/FavoritoMenuService'
|
|
31
|
+
import { TFavoritoMenuResponse } from '@base/@types/api/response/dadosMestres/TFavoritoMenuResponse'
|
|
18
32
|
|
|
19
33
|
type SearchData = {
|
|
20
34
|
key: string
|
|
@@ -55,7 +69,10 @@ const filterNavigationByAuthority = (
|
|
|
55
69
|
if (filteredSubMenu.length > 0) {
|
|
56
70
|
acc.push({ ...nav, subMenu: filteredSubMenu })
|
|
57
71
|
}
|
|
58
|
-
} else if (
|
|
72
|
+
} else if (
|
|
73
|
+
(nav.path && userAuthority.includes(nav.path)) ||
|
|
74
|
+
!nav.path
|
|
75
|
+
) {
|
|
59
76
|
acc.push(nav)
|
|
60
77
|
}
|
|
61
78
|
|
|
@@ -71,49 +88,110 @@ const ListItem = (props: {
|
|
|
71
88
|
keyWord: string
|
|
72
89
|
breadcrumbs: string[]
|
|
73
90
|
onNavigate: () => void
|
|
91
|
+
menuKey: string
|
|
92
|
+
isFavorited: boolean
|
|
93
|
+
favToggling: boolean
|
|
94
|
+
onToggleFavorite: (menuKey: string) => void
|
|
74
95
|
}) => {
|
|
75
|
-
const {
|
|
96
|
+
const {
|
|
97
|
+
icon,
|
|
98
|
+
label,
|
|
99
|
+
url = '',
|
|
100
|
+
keyWord,
|
|
101
|
+
onNavigate,
|
|
102
|
+
breadcrumbs,
|
|
103
|
+
menuKey,
|
|
104
|
+
isFavorited,
|
|
105
|
+
favToggling,
|
|
106
|
+
onToggleFavorite,
|
|
107
|
+
} = props
|
|
76
108
|
|
|
77
109
|
const makeBreadcrumbs = (breadcrumbs: string[]) => {
|
|
78
110
|
return breadcrumbs.join(' / ')
|
|
79
111
|
}
|
|
80
112
|
|
|
81
113
|
return (
|
|
82
|
-
<
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
className=
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
'underline bg-transparent font-semibold dark:text-white',
|
|
103
|
-
)}
|
|
104
|
-
searchWords={[keyWord]}
|
|
105
|
-
textToHighlight={makeBreadcrumbs(breadcrumbs)}
|
|
106
|
-
/>
|
|
114
|
+
<div className="flex items-center gap-1 pr-4">
|
|
115
|
+
<Link to={url} onClick={onNavigate} className="flex-1">
|
|
116
|
+
<div
|
|
117
|
+
className={classNames(
|
|
118
|
+
'flex items-center justify-between rounded-xl p-3 cursor-pointer user-select',
|
|
119
|
+
'hover:bg-gray-100 dark:hover:bg-gray-700',
|
|
120
|
+
)}
|
|
121
|
+
>
|
|
122
|
+
<div className="w-72">
|
|
123
|
+
<div className="overflow-hidden text-gray-900 dark:text-gray-300">
|
|
124
|
+
<Highlighter
|
|
125
|
+
autoEscape
|
|
126
|
+
highlightClassName={classNames(
|
|
127
|
+
'text-primary',
|
|
128
|
+
'underline bg-transparent font-semibold dark:text-white',
|
|
129
|
+
)}
|
|
130
|
+
searchWords={[keyWord]}
|
|
131
|
+
textToHighlight={makeBreadcrumbs(breadcrumbs)}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
107
134
|
</div>
|
|
135
|
+
<HiChevronRight className="shrink-0 text-lg" />
|
|
108
136
|
</div>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
</Link>
|
|
138
|
+
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
className="flex-shrink-0 rounded-lg p-2 text-lg transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
142
|
+
disabled={favToggling}
|
|
143
|
+
onClick={(e) => {
|
|
144
|
+
e.preventDefault()
|
|
145
|
+
e.stopPropagation()
|
|
146
|
+
onToggleFavorite(menuKey)
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{favToggling ? (
|
|
150
|
+
<Spinner size={18} />
|
|
151
|
+
) : isFavorited ? (
|
|
152
|
+
<HiStar className="text-yellow-500" />
|
|
153
|
+
) : (
|
|
154
|
+
<HiOutlineStar className="text-gray-400 hover:text-yellow-500" />
|
|
155
|
+
)}
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
112
158
|
)
|
|
113
159
|
}
|
|
114
160
|
|
|
115
161
|
type NavigationTreeFlat = NavigationTree & { parents: string[] }
|
|
116
162
|
|
|
163
|
+
type NavItemInfo = {
|
|
164
|
+
key: string
|
|
165
|
+
path: string
|
|
166
|
+
title: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const flattenNavTree = (
|
|
170
|
+
navTree: NavigationTree[],
|
|
171
|
+
): Map<string, NavItemInfo> => {
|
|
172
|
+
const map = new Map<string, NavItemInfo>()
|
|
173
|
+
|
|
174
|
+
const walk = (items: NavigationTree[]) => {
|
|
175
|
+
for (const item of items) {
|
|
176
|
+
if (item.type === NAV_ITEM_TYPE_ITEM && item.path) {
|
|
177
|
+
map.set(item.key, {
|
|
178
|
+
key: item.key,
|
|
179
|
+
path: item.path,
|
|
180
|
+
title: item.title,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
if (item.subMenu && item.subMenu.length > 0) {
|
|
184
|
+
walk(item.subMenu)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
walk(navTree)
|
|
190
|
+
return map
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const navItemMap = flattenNavTree(navigationConfig)
|
|
194
|
+
|
|
117
195
|
const _Search = ({ className }: { className?: string }) => {
|
|
118
196
|
const [loading, setLoading] = useState(false)
|
|
119
197
|
const [searchDialogOpen, setSearchDialogOpen] = useState(false)
|
|
@@ -121,6 +199,124 @@ const _Search = ({ className }: { className?: string }) => {
|
|
|
121
199
|
const [noResult, setNoResult] = useState(false)
|
|
122
200
|
const userAuthority = useSessionUser((state) => state.user.authority)
|
|
123
201
|
|
|
202
|
+
// -- Favorites state --
|
|
203
|
+
const [favDialogOpen, setFavDialogOpen] = useState(false)
|
|
204
|
+
const [favLoading, setFavLoading] = useState(false)
|
|
205
|
+
const [favorites, setFavorites] = useState<TFavoritoMenuResponse[]>([])
|
|
206
|
+
const [removingId, setRemovingId] = useState<number | null>(null)
|
|
207
|
+
const [favoritedKeys, setFavoritedKeys] = useState<Set<string>>(new Set())
|
|
208
|
+
const [togglingKeys, setTogglingKeys] = useState<Set<string>>(new Set())
|
|
209
|
+
const userId = useSessionUser((state) => state.user.userId)
|
|
210
|
+
|
|
211
|
+
const loadFavorites = async () => {
|
|
212
|
+
if (!userId) return
|
|
213
|
+
setFavLoading(true)
|
|
214
|
+
try {
|
|
215
|
+
const data = await apiFindFavoritosByUsuario(Number(userId))
|
|
216
|
+
setFavorites(data)
|
|
217
|
+
setFavoritedKeys(new Set(data.map((f) => f.menuKey)))
|
|
218
|
+
} catch {
|
|
219
|
+
toast.push(
|
|
220
|
+
<Notification title="Erro" type="danger">
|
|
221
|
+
Falha ao carregar favoritos.
|
|
222
|
+
</Notification>,
|
|
223
|
+
)
|
|
224
|
+
setFavorites([])
|
|
225
|
+
setFavoritedKeys(new Set())
|
|
226
|
+
} finally {
|
|
227
|
+
setFavLoading(false)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const handleFavRemove = async (id: number) => {
|
|
232
|
+
setRemovingId(id)
|
|
233
|
+
try {
|
|
234
|
+
await apiDeleteFavoritoMenu(id)
|
|
235
|
+
setFavorites((prev) => prev.filter((f) => f.id !== id))
|
|
236
|
+
setFavoritedKeys((prev) => {
|
|
237
|
+
const next = new Set(prev)
|
|
238
|
+
const removed = favorites.find((f) => f.id === id)
|
|
239
|
+
if (removed) next.delete(removed.menuKey)
|
|
240
|
+
return next
|
|
241
|
+
})
|
|
242
|
+
} catch {
|
|
243
|
+
toast.push(
|
|
244
|
+
<Notification title="Erro" type="danger">
|
|
245
|
+
Falha ao remover favorito.
|
|
246
|
+
</Notification>,
|
|
247
|
+
)
|
|
248
|
+
} finally {
|
|
249
|
+
setRemovingId(null)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const handleFavToggle = async (menuKey: string) => {
|
|
254
|
+
if (!userId || togglingKeys.has(menuKey)) return
|
|
255
|
+
setTogglingKeys((prev) => new Set(prev).add(menuKey))
|
|
256
|
+
try {
|
|
257
|
+
if (favoritedKeys.has(menuKey)) {
|
|
258
|
+
const fav = favorites.find((f) => f.menuKey === menuKey)
|
|
259
|
+
if (fav) {
|
|
260
|
+
await apiDeleteFavoritoMenu(fav.id)
|
|
261
|
+
setFavorites((prev) => prev.filter((f) => f.id !== fav.id))
|
|
262
|
+
setFavoritedKeys((prev) => {
|
|
263
|
+
const next = new Set(prev)
|
|
264
|
+
next.delete(menuKey)
|
|
265
|
+
return next
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
const created = await apiCreateFavoritoMenu({ menuKey })
|
|
270
|
+
setFavorites((prev) => [...prev, created])
|
|
271
|
+
setFavoritedKeys((prev) => new Set(prev).add(menuKey))
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
toast.push(
|
|
275
|
+
<Notification title="Erro" type="danger">
|
|
276
|
+
{favoritedKeys.has(menuKey)
|
|
277
|
+
? 'Falha ao remover favorito.'
|
|
278
|
+
: 'Falha ao adicionar favorito.'}
|
|
279
|
+
</Notification>,
|
|
280
|
+
)
|
|
281
|
+
} finally {
|
|
282
|
+
setTogglingKeys((prev) => {
|
|
283
|
+
const next = new Set(prev)
|
|
284
|
+
next.delete(menuKey)
|
|
285
|
+
return next
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const handleFavOpen = () => {
|
|
291
|
+
setFavDialogOpen(true)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const handleFavClose = () => {
|
|
295
|
+
setFavDialogOpen(false)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
if (favDialogOpen) {
|
|
300
|
+
loadFavorites()
|
|
301
|
+
}
|
|
302
|
+
}, [favDialogOpen, userId])
|
|
303
|
+
|
|
304
|
+
// Also load favorites when search opens (for star toggles in results)
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
if (searchDialogOpen && userId) {
|
|
307
|
+
loadFavorites()
|
|
308
|
+
}
|
|
309
|
+
}, [searchDialogOpen, userId])
|
|
310
|
+
|
|
311
|
+
const enrichedFavorites = useMemo(() => {
|
|
312
|
+
return favorites.map((fav) => ({
|
|
313
|
+
...fav,
|
|
314
|
+
navInfo: navItemMap.get(fav.menuKey),
|
|
315
|
+
}))
|
|
316
|
+
}, [favorites])
|
|
317
|
+
|
|
318
|
+
// -- end favorites state --
|
|
319
|
+
|
|
124
320
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
125
321
|
|
|
126
322
|
const handleReset = () => {
|
|
@@ -243,12 +439,21 @@ const _Search = ({ className }: { className?: string }) => {
|
|
|
243
439
|
|
|
244
440
|
return (
|
|
245
441
|
<>
|
|
246
|
-
<div
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
442
|
+
<div className="flex items-center gap-1">
|
|
443
|
+
<div
|
|
444
|
+
className={classNames(className, 'text-2xl')}
|
|
445
|
+
onClick={handleSearchOpen}
|
|
446
|
+
>
|
|
447
|
+
<PiMagnifyingGlassDuotone />
|
|
448
|
+
</div>
|
|
449
|
+
<div
|
|
450
|
+
className={classNames(className, 'text-2xl')}
|
|
451
|
+
onClick={handleFavOpen}
|
|
452
|
+
>
|
|
453
|
+
<HiOutlineStar />
|
|
454
|
+
</div>
|
|
251
455
|
</div>
|
|
456
|
+
{/* Search dialog */}
|
|
252
457
|
<Dialog
|
|
253
458
|
contentClassName="p-0"
|
|
254
459
|
isOpen={searchDialogOpen}
|
|
@@ -262,7 +467,9 @@ const _Search = ({ className }: { className?: string }) => {
|
|
|
262
467
|
<input
|
|
263
468
|
ref={inputRef}
|
|
264
469
|
className="block w-full bg-transparent p-4 text-base text-gray-900 outline-none ring-0 dark:text-gray-100"
|
|
265
|
-
placeholder={t(
|
|
470
|
+
placeholder={t(
|
|
471
|
+
'common.input.placeholder.search',
|
|
472
|
+
)}
|
|
266
473
|
onChange={handleSearch}
|
|
267
474
|
/>
|
|
268
475
|
</div>
|
|
@@ -272,58 +479,134 @@ const _Search = ({ className }: { className?: string }) => {
|
|
|
272
479
|
</div>
|
|
273
480
|
<div className="px-5 py-6">
|
|
274
481
|
{loading ? (
|
|
275
|
-
<div
|
|
276
|
-
|
|
277
|
-
|
|
482
|
+
<div className="flex items-center justify-center">
|
|
483
|
+
<Spinner size={40} />
|
|
484
|
+
</div>
|
|
485
|
+
) : (
|
|
486
|
+
<ScrollBar className="max-h-[350px] overflow-y-auto">
|
|
487
|
+
{searchResult.map((result) => (
|
|
488
|
+
<div key={result.title} className="mb-4">
|
|
489
|
+
<h6 className="mb-3">{result.title}</h6>
|
|
490
|
+
{result.data.map((data, index) => (
|
|
491
|
+
<ListItem
|
|
492
|
+
key={data.title + index}
|
|
493
|
+
icon={data.icon}
|
|
494
|
+
breadcrumbs={data.breadcrumbs}
|
|
495
|
+
label={data.title}
|
|
496
|
+
url={data.path}
|
|
497
|
+
keyWord={
|
|
498
|
+
inputRef.current?.value ||
|
|
499
|
+
''
|
|
500
|
+
}
|
|
501
|
+
onNavigate={handleNavigate}
|
|
502
|
+
menuKey={data.key}
|
|
503
|
+
isFavorited={favoritedKeys.has(
|
|
504
|
+
data.key,
|
|
505
|
+
)}
|
|
506
|
+
favToggling={togglingKeys.has(
|
|
507
|
+
data.key,
|
|
508
|
+
)}
|
|
509
|
+
onToggleFavorite={
|
|
510
|
+
handleFavToggle
|
|
511
|
+
}
|
|
512
|
+
/>
|
|
513
|
+
))}
|
|
514
|
+
</div>
|
|
515
|
+
))}
|
|
516
|
+
{searchResult.length === 0 && noResult && (
|
|
517
|
+
<div className="my-10 text-center text-lg">
|
|
518
|
+
<span className="pr-1">
|
|
519
|
+
{t('common.status.noResultsFor')}
|
|
520
|
+
</span>
|
|
521
|
+
<span className="heading-text">
|
|
522
|
+
{`'`}
|
|
523
|
+
{inputRef.current?.value}
|
|
524
|
+
{`'`}
|
|
525
|
+
</span>
|
|
526
|
+
</div>
|
|
278
527
|
)}
|
|
279
|
-
>
|
|
528
|
+
</ScrollBar>
|
|
529
|
+
)}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
</Dialog>
|
|
533
|
+
{/* Favorites dialog */}
|
|
534
|
+
<Dialog
|
|
535
|
+
contentClassName="p-0"
|
|
536
|
+
isOpen={favDialogOpen}
|
|
537
|
+
closable={false}
|
|
538
|
+
onRequestClose={handleFavClose}
|
|
539
|
+
>
|
|
540
|
+
<div>
|
|
541
|
+
<div className="flex items-center justify-between border-b border-gray-200 px-4 py-4 dark:border-gray-600">
|
|
542
|
+
<div className="flex items-center gap-2">
|
|
543
|
+
<HiStar className="text-xl text-yellow-500" />
|
|
544
|
+
<h6 className="text-base font-semibold text-gray-900 dark:text-gray-100">
|
|
545
|
+
Favoritos
|
|
546
|
+
</h6>
|
|
547
|
+
</div>
|
|
548
|
+
<Button size="xs" onClick={handleFavClose}>
|
|
549
|
+
Esc
|
|
550
|
+
</Button>
|
|
551
|
+
</div>
|
|
552
|
+
<div className="px-5 py-6">
|
|
553
|
+
{favLoading ? (
|
|
554
|
+
<div className="flex items-center justify-center py-10">
|
|
280
555
|
<Spinner size={40} />
|
|
281
556
|
</div>
|
|
282
557
|
) : (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
data.breadcrumbs
|
|
558
|
+
<ScrollBar className="max-h-[350px] overflow-y-auto">
|
|
559
|
+
{enrichedFavorites.length === 0 ? (
|
|
560
|
+
<div className="my-10 text-center text-lg text-gray-500">
|
|
561
|
+
Nenhum favorito salvo.
|
|
562
|
+
</div>
|
|
563
|
+
) : (
|
|
564
|
+
<div className="flex flex-col gap-1">
|
|
565
|
+
{enrichedFavorites.map((fav) => (
|
|
566
|
+
<div
|
|
567
|
+
key={fav.id}
|
|
568
|
+
className="flex items-center gap-1 pr-4"
|
|
569
|
+
>
|
|
570
|
+
<Link
|
|
571
|
+
to={
|
|
572
|
+
fav.navInfo?.path || '#'
|
|
299
573
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
574
|
+
className="flex-1"
|
|
575
|
+
onClick={handleFavClose}
|
|
576
|
+
>
|
|
577
|
+
<div className="user-select flex cursor-pointer items-center justify-between rounded-xl p-3 hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
578
|
+
<div className="w-72">
|
|
579
|
+
<div className="overflow-hidden text-gray-900 dark:text-gray-300">
|
|
580
|
+
{fav.navInfo
|
|
581
|
+
?.title ||
|
|
582
|
+
fav.menuKey}
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
<HiChevronRight className="h-6 w-6 shrink-0" />
|
|
586
|
+
</div>
|
|
587
|
+
</Link>
|
|
588
|
+
<button
|
|
589
|
+
type="button"
|
|
590
|
+
className="flex-shrink-0 rounded-lg p-2 text-lg text-gray-400 transition-colors hover:bg-gray-100 hover:text-red-500 dark:hover:bg-gray-700"
|
|
591
|
+
disabled={
|
|
592
|
+
removingId === fav.id
|
|
305
593
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
</
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
</span>
|
|
323
|
-
</div>
|
|
324
|
-
)}
|
|
325
|
-
</ScrollBar>
|
|
326
|
-
</>
|
|
594
|
+
onClick={(e) => {
|
|
595
|
+
e.stopPropagation()
|
|
596
|
+
handleFavRemove(fav.id)
|
|
597
|
+
}}
|
|
598
|
+
>
|
|
599
|
+
{removingId === fav.id ? (
|
|
600
|
+
<Spinner size={18} />
|
|
601
|
+
) : (
|
|
602
|
+
<HiOutlineTrash />
|
|
603
|
+
)}
|
|
604
|
+
</button>
|
|
605
|
+
</div>
|
|
606
|
+
))}
|
|
607
|
+
</div>
|
|
608
|
+
)}
|
|
609
|
+
</ScrollBar>
|
|
327
610
|
)}
|
|
328
611
|
</div>
|
|
329
612
|
</div>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { mock } from '../MockAdapter'
|
|
2
|
+
import { favoritosMenuData } from '../data/favoritosMenuData'
|
|
3
|
+
import { TFavoritoMenuResponse } from '@base/@types/api/response/dadosMestres/TFavoritoMenuResponse'
|
|
4
|
+
|
|
5
|
+
const BASE_URL = '/dados-mestres/api/favoritos-menu'
|
|
6
|
+
|
|
7
|
+
const randomDelay = () => 300 + Math.random() * 1200 // 300-1500ms
|
|
8
|
+
|
|
9
|
+
type MockConfig = { data?: string; url?: string }
|
|
10
|
+
let favoritos: TFavoritoMenuResponse[] = [...favoritosMenuData]
|
|
11
|
+
let nextId = favoritos.length + 1
|
|
12
|
+
|
|
13
|
+
function withDelay<T>(fn: (config: MockConfig) => T): (config: MockConfig) => Promise<T> {
|
|
14
|
+
return (config) => {
|
|
15
|
+
const { promise, resolve } = Promise.withResolvers<T>()
|
|
16
|
+
setTimeout(() => resolve(fn(config)), randomDelay())
|
|
17
|
+
return promise
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// POST / — criar favorito
|
|
22
|
+
mock.onPost(BASE_URL).reply(withDelay((config) => {
|
|
23
|
+
const body = JSON.parse(config.data ?? '{}')
|
|
24
|
+
const menuKey: string = body.menuKey
|
|
25
|
+
|
|
26
|
+
if (!menuKey) {
|
|
27
|
+
return [400, { status: 400, title: 'Bad Request', detail: 'menuKey is required' }]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const exists = favoritos.find((f) => f.menuKey === menuKey && f.ativo)
|
|
31
|
+
if (exists) {
|
|
32
|
+
return [400, { status: 400, title: 'Bad Request', detail: 'Menu already favorited' }]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const novo: TFavoritoMenuResponse = {
|
|
36
|
+
id: nextId++,
|
|
37
|
+
usuarioId: 1,
|
|
38
|
+
menuKey,
|
|
39
|
+
ordem: favoritos.filter((f) => f.ativo).length,
|
|
40
|
+
ativo: true,
|
|
41
|
+
}
|
|
42
|
+
favoritos.push(novo)
|
|
43
|
+
return [200, novo]
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
// GET /usuario/{usuarioId} — listar favoritos
|
|
47
|
+
mock.onGet(new RegExp(`${BASE_URL}/usuario/\\d+`)).reply(withDelay(() => {
|
|
48
|
+
const ativos = favoritos
|
|
49
|
+
.filter((f) => f.ativo)
|
|
50
|
+
.sort((a, b) => a.ordem - b.ordem)
|
|
51
|
+
return [200, ativos]
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
// DELETE /{id} — remover (soft-delete)
|
|
55
|
+
mock.onDelete(new RegExp(`${BASE_URL}/\\d+`)).reply(withDelay((config) => {
|
|
56
|
+
const id = Number(config.url?.split('/').pop())
|
|
57
|
+
const fav = favoritos.find((f) => f.id === id)
|
|
58
|
+
|
|
59
|
+
if (!fav || !fav.ativo) {
|
|
60
|
+
return [400, { status: 400, title: 'Bad Request', detail: 'Favorito not found' }]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fav.ativo = false
|
|
64
|
+
return [204]
|
|
65
|
+
}))
|
|
66
|
+
|
|
67
|
+
// PUT /reorder — reordenar
|
|
68
|
+
mock.onPut(`${BASE_URL}/reorder`).reply(withDelay((config) => {
|
|
69
|
+
const body = JSON.parse(config.data ?? '{}')
|
|
70
|
+
const orderedIds: number[] = body.orderedIds
|
|
71
|
+
|
|
72
|
+
if (!orderedIds || orderedIds.length === 0) {
|
|
73
|
+
return [400, { status: 400, title: 'Bad Request', detail: 'orderedIds is required' }]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const reordered: TFavoritoMenuResponse[] = []
|
|
77
|
+
for (let i = 0; i < orderedIds.length; i++) {
|
|
78
|
+
const fav = favoritos.find((f) => f.id === orderedIds[i] && f.ativo)
|
|
79
|
+
if (!fav) {
|
|
80
|
+
return [400, { status: 400, title: 'Bad Request', detail: `Favorito ${orderedIds[i]} not found` }]
|
|
81
|
+
}
|
|
82
|
+
fav.ordem = i
|
|
83
|
+
reordered.push(fav)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [200, reordered]
|
|
87
|
+
}))
|
package/lib/@ecme/mock/index.ts
CHANGED