@mistertemp/libs-front-shared 3.0.3 → 4.0.0-alpha.0

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 (28) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +10 -1
  3. package/src/components/Notifications/CustomerNotificationCenterClient.ts +195 -0
  4. package/src/components/Notifications/EmptyNotificationsList.tsx +33 -0
  5. package/src/components/Notifications/Notification.tsx +105 -0
  6. package/src/components/Notifications/NotificationContent.tsx +153 -0
  7. package/src/components/Notifications/Notifications.module.scss +345 -0
  8. package/src/components/Notifications/NotificationsBell.tsx +242 -0
  9. package/src/components/Notifications/NotificationsCenterContext.tsx +578 -0
  10. package/src/components/Notifications/NotificationsList.tsx +293 -0
  11. package/src/components/Notifications/PopupNotifications.module.scss +73 -0
  12. package/src/components/Notifications/PopupNotifications.tsx +42 -0
  13. package/src/components/Notifications/dateISOToString.ts +32 -0
  14. package/src/components/Notifications/index.ts +7 -0
  15. package/src/components/Notifications/types.ts +39 -0
  16. package/src/components/Notifications/useLiveNotifications.ts +122 -0
  17. package/src/components/TextFilter/TextFilter.tsx +1 -8
  18. package/src/components/index.ts +1 -0
  19. package/src/locales/es/date-picker.json +11 -0
  20. package/src/locales/es/notification-error.json +12 -0
  21. package/src/locales/es/notification-utils.json +7 -0
  22. package/src/locales/es/notification.json +17 -0
  23. package/src/locales/fr/notification-error.json +12 -0
  24. package/src/locales/fr/notification-utils.json +7 -0
  25. package/src/locales/fr/notification.json +17 -0
  26. package/src/locales/it/notification-error.json +12 -0
  27. package/src/locales/it/notification-utils.json +7 -0
  28. package/src/locales/it/notification.json +17 -0
@@ -0,0 +1,293 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ ChevronDownSvg,
5
+ ChevronUpSvg,
6
+ DatePicker,
7
+ Dropdown,
8
+ Loader,
9
+ Tabs,
10
+ Tag,
11
+ } from '@mistertemp/design-system';
12
+ import { MixedDatePickerSelection } from '@mistertemp/design-system/src/components/design-system/DatePicker/DatePicker';
13
+ import classNames from 'classnames';
14
+ import { fr, it } from 'date-fns/locale';
15
+ import { TFunction } from 'i18next';
16
+ import React, { FC, useContext, useMemo, useState } from 'react';
17
+ import { DateRange } from 'react-day-picker';
18
+
19
+ import { useBundledTranslation } from '../../hooks';
20
+ import datePickerEs from '../../locales/es/date-picker.json';
21
+ import datePickerFr from '../../locales/fr/date-picker.json';
22
+ import datePickerIt from '../../locales/it/date-picker.json';
23
+ import notificationEs from '../../locales/es/notification.json';
24
+ import notificationFr from '../../locales/fr/notification.json';
25
+ import notificationIt from '../../locales/it/notification.json';
26
+ import EmptyNotificationsList from './EmptyNotificationsList';
27
+ import { Notification } from './Notification';
28
+ import { NotificationsCenterContext } from './NotificationsCenterContext';
29
+ import styles from './Notifications.module.scss';
30
+
31
+ const dataTestId = 'notificationsList';
32
+ const translationNs = 'notification';
33
+ const notificationTranslations = { fr: notificationFr, it: notificationIt, es: notificationEs };
34
+
35
+ interface NotificationsListProps {
36
+ /**
37
+ * Locale used for the date picker component.
38
+ * @default fr (French)
39
+ */
40
+ locale?: string;
41
+ }
42
+
43
+ export const NotificationsList: FC<NotificationsListProps> = ({ locale: localeStr }) => {
44
+ const { t } = useBundledTranslation(translationNs, notificationTranslations);
45
+ const { t: tDate } = useBundledTranslation('date-picker', {
46
+ fr: datePickerFr,
47
+ it: datePickerIt,
48
+ es: datePickerEs,
49
+ });
50
+
51
+ const {
52
+ unreadNotifications,
53
+ notifications,
54
+ isFullPageMode,
55
+ startDate,
56
+ setStartDate,
57
+ endDate,
58
+ setEndDate,
59
+ setIsDisplayReadNotification,
60
+ markAllAsRead,
61
+ setCategories,
62
+ categories,
63
+ notificationsHasBeenLoaded,
64
+ } = useContext(NotificationsCenterContext);
65
+
66
+ const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
67
+
68
+ const dateLocale = useMemo(
69
+ () => (localeStr === 'it-IT' ? it : fr),
70
+ [localeStr],
71
+ );
72
+
73
+ const tabs = useMemo(() => {
74
+ const tabs = [
75
+ {
76
+ label: t('notification:notificationsPopover.all'),
77
+ path: 'tab_all',
78
+ },
79
+ {
80
+ label: t('notification:notificationsPopover.unread'),
81
+ path: 'tab_unread',
82
+ },
83
+ ];
84
+
85
+ if (isFullPageMode) {
86
+ tabs.push({
87
+ label: t('notification:notificationsPopover.read'),
88
+ path: 'tab_read',
89
+ });
90
+ }
91
+
92
+ return tabs;
93
+ }, [isFullPageMode, t]);
94
+
95
+ const [selectedDate, setSelectedDate] = useState<MixedDatePickerSelection>([new Date()]);
96
+
97
+ const handleOnChangeTab = (value: string) => {
98
+ switch (value) {
99
+ case 'tab_all':
100
+ setIsDisplayReadNotification(undefined);
101
+ break;
102
+ case 'tab_read':
103
+ setIsDisplayReadNotification(true);
104
+ break;
105
+ case 'tab_unread':
106
+ setIsDisplayReadNotification(false);
107
+ break;
108
+ default:
109
+ setIsDisplayReadNotification(undefined);
110
+ break;
111
+ }
112
+ };
113
+
114
+ const handleAllMarkAsRead = () => {
115
+ markAllAsRead();
116
+ };
117
+
118
+ const handleReinitFilters = () => {
119
+ setStartDate(undefined);
120
+ setEndDate(undefined);
121
+ setCategories([]);
122
+ setSelectedDate(new Date());
123
+ };
124
+
125
+ const handleOpenChange = (isDatePickerOpen: boolean) => {
126
+ setIsDatePickerOpen(isDatePickerOpen);
127
+ };
128
+
129
+ const handleOnCancel = () => {
130
+ setIsDatePickerOpen(false);
131
+ };
132
+
133
+ const handleOnSelect = (date: Date | Date[] | DateRange | undefined) => {
134
+ setSelectedDate(date);
135
+ };
136
+
137
+ const handleOnSubmit = (value: Date | Array<Date> | DateRange) => {
138
+ if (value && typeof value === 'object' && 'from' in value && 'to' in value) {
139
+ if (value.from) {
140
+ const startDate = new Date(
141
+ Date.UTC(
142
+ value.from.getFullYear(),
143
+ value.from.getMonth(),
144
+ value.from.getDate(),
145
+ 0,
146
+ 0,
147
+ 0,
148
+ 0,
149
+ ),
150
+ );
151
+
152
+ setStartDate(startDate);
153
+ }
154
+
155
+ if (value.to) {
156
+ const endDate = new Date(
157
+ Date.UTC(
158
+ value.to.getFullYear(),
159
+ value.to.getMonth(),
160
+ value.to.getDate(),
161
+ 23,
162
+ 59,
163
+ 59,
164
+ 999,
165
+ ),
166
+ );
167
+
168
+ setEndDate(endDate);
169
+ }
170
+ }
171
+ setIsDatePickerOpen(false);
172
+ };
173
+
174
+ const iconName = isDatePickerOpen ? ChevronUpSvg : ChevronDownSvg;
175
+
176
+ return (
177
+ <div data-testid={`${dataTestId}`} className={styles.notificationsListBody}>
178
+ <div className={styles.notiticationsListFilters}>
179
+ <Tabs
180
+ data-testid={`${dataTestId}+tabs`}
181
+ onChange={handleOnChangeTab}
182
+ size="sm"
183
+ tabs={tabs}
184
+ value={
185
+ isFullPageMode ? 'tab_all' : 'tab_unread'
186
+ }
187
+ variant={isFullPageMode ? 'buttons' : 'underline'}
188
+ className={styles.tabs}
189
+ />
190
+ {unreadNotifications > 0 && !isFullPageMode && (
191
+ <Badge
192
+ data-testid={`${dataTestId}+unreadBadge`}
193
+ className={styles.notificationsListUnreadBadge}
194
+ text={unreadNotifications}
195
+ color="primary"
196
+ size="sm"
197
+ />
198
+ )}
199
+ {isFullPageMode && (
200
+ <Button
201
+ data-testid={`${dataTestId}+markAllAsReadButton`}
202
+ color="secondary-gray"
203
+ size="small"
204
+ text={t('notification:notificationsPopover.markAllAsRead')}
205
+ onClick={handleAllMarkAsRead}
206
+ disabled={unreadNotifications === 0}
207
+ className={styles.notificationsBtnMarkAllRead}
208
+ />
209
+ )}
210
+ {isFullPageMode && (
211
+ <Dropdown
212
+ isOpen={isDatePickerOpen}
213
+ onOpenChange={handleOpenChange}
214
+ onDropdownCTAClick={() => {}}
215
+ data-testid={`${dataTestId}+dropDownDate`}
216
+ trigger={
217
+ <Tag
218
+ data-testid={`${dataTestId}+dropDownDate-Tag`}
219
+ label={t('notification:notificationsExtended.date')}
220
+ rightIcon={iconName}
221
+ size="lg"
222
+ />
223
+ }
224
+ className={styles.notificationsDropDownDate}>
225
+ <DatePicker
226
+ t={tDate as TFunction}
227
+ locale={dateLocale}
228
+ selected={selectedDate}
229
+ data-testid={`${dataTestId}+date-picker`}
230
+ mode={'range'}
231
+ onSelect={handleOnSelect}
232
+ onSubmit={handleOnSubmit}
233
+ onCancel={handleOnCancel}
234
+ />
235
+ </Dropdown>
236
+ )}
237
+ {isFullPageMode && (
238
+ <Button
239
+ data-testid={`${dataTestId}+ReinitFilterButton`}
240
+ color="tertiary-color"
241
+ size="small"
242
+ text={t('notification:notificationsExtended.reinitFilters')}
243
+ onClick={handleReinitFilters}
244
+ disabled={
245
+ startDate == undefined && endDate == undefined && categories.length == 0
246
+ }
247
+ />
248
+ )}
249
+ </div>
250
+ <div
251
+ className={classNames(
252
+ isFullPageMode
253
+ ? styles.notificationsListFullPageMode
254
+ : styles.notificationsList,
255
+ )}
256
+ data-testid={`${dataTestId}+list-container`}>
257
+ <div
258
+ className={classNames(
259
+ isFullPageMode && styles.notificationListRoundedContainer,
260
+ )}>
261
+ {!notificationsHasBeenLoaded ? (
262
+ <div
263
+ data-testid="notificationsList+Loader"
264
+ className={styles.notificationsListLoader}>
265
+ <Loader />
266
+ </div>
267
+ ) : notifications.length > 0 ? (
268
+ notifications.map((notification, rank) => (
269
+ <Notification
270
+ data-testid={`${dataTestId}+`}
271
+ key={notification.notificationId}
272
+ id={notification.notificationId}
273
+ title={notification.title}
274
+ createdAt={notification.createdAt}
275
+ content={notification.message}
276
+ link={notification.link}
277
+ isUnread={notification.readAt === undefined}
278
+ notificationType={notification.notificationType}
279
+ rank={rank}
280
+ aggregatedSubtitle={notification.aggregatedSubtitle}
281
+ aggregatedNotifications={notification.aggregatedNotifications}
282
+ />
283
+ ))
284
+ ) : (
285
+ <EmptyNotificationsList />
286
+ )}
287
+ </div>
288
+ </div>
289
+ </div>
290
+ );
291
+ };
292
+
293
+ export default NotificationsList;
@@ -0,0 +1,73 @@
1
+ .popupNotificationsContainer {
2
+ position: fixed;
3
+ top: 6.4rem;
4
+ right: -84rem;
5
+ display: flex;
6
+ width: 80rem;
7
+ height: 1000px;
8
+ flex-direction: column;
9
+ padding-right: 0.5rem;
10
+ padding-left: 0.5rem;
11
+ margin-top: 0.8rem;
12
+ }
13
+
14
+ .popupNotifications {
15
+ width: 36rem;
16
+ padding-left: 0.8rem;
17
+ border-radius: 0.8rem;
18
+ margin-top: 0.5rem;
19
+ margin-left: 40rem;
20
+ animation: bounce-in-left 1.1s both;
21
+ background-color: white;
22
+ box-shadow: 0 4px 8px -2px #1018281a;
23
+ }
24
+
25
+ .displayed {
26
+ margin-left: 0;
27
+ opacity: 1;
28
+ }
29
+
30
+ .hidden {
31
+ animation: bounce-out-left 1.1s both;
32
+ }
33
+
34
+ @keyframes bounce-in-left {
35
+ 0% {
36
+ animation-timing-function: ease-in;
37
+ opacity: 0;
38
+ transform: translateX(0);
39
+ }
40
+
41
+ 60% {
42
+ animation-timing-function: ease-out;
43
+ opacity: 1;
44
+ transform: translateX(-44rem);
45
+ }
46
+
47
+ 80% {
48
+ animation-timing-function: ease-in;
49
+ transform: translateX(-40.4rem);
50
+ }
51
+
52
+ 100% {
53
+ animation-timing-function: ease-out;
54
+ transform: translateX(-44rem);
55
+ }
56
+ }
57
+
58
+ @keyframes bounce-out-left {
59
+ 0% {
60
+ animation-timing-function: ease-in;
61
+ transform: translateX(-44rem);
62
+ }
63
+
64
+ 60% {
65
+ animation-timing-function: ease-in;
66
+ transform: translateX(0);
67
+ }
68
+
69
+ 100% {
70
+ animation-timing-function: ease-in;
71
+ transform: translateX(0);
72
+ }
73
+ }
@@ -0,0 +1,42 @@
1
+ import classnames from 'classnames';
2
+ import React, { FC, useContext } from 'react';
3
+
4
+ import { NotificationsCenterContext } from './NotificationsCenterContext';
5
+ import { Notification } from './Notification';
6
+ import styles from './PopupNotifications.module.scss';
7
+
8
+ export const PopupNotifications: FC = () => {
9
+ const { popupNotifications, closePopupNotification } = useContext(NotificationsCenterContext);
10
+
11
+ return (
12
+ <div className={styles.popupNotificationsContainer}>
13
+ {[...popupNotifications].map((notification, rank) => (
14
+ <div
15
+ key={notification.notificationId}
16
+ className={classnames(
17
+ styles.popupNotifications,
18
+ notification.displayed ? styles.displayed : '',
19
+ notification.hidden ? styles.hidden : '',
20
+ )}>
21
+ <Notification
22
+ data-testid={`popupNotifications+${notification.notificationId}`}
23
+ key={notification.notificationId}
24
+ id={notification.notificationId}
25
+ title={notification.title}
26
+ createdAt={notification.createdAt}
27
+ content={notification.message}
28
+ onClose={() => closePopupNotification(notification)}
29
+ link={notification.link}
30
+ isUnread={false}
31
+ notificationType={notification.notificationType}
32
+ rank={rank}
33
+ aggregatedSubtitle={notification.aggregatedSubtitle}
34
+ aggregatedNotifications={notification.aggregatedNotifications}
35
+ />
36
+ </div>
37
+ ))}
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default PopupNotifications;
@@ -0,0 +1,32 @@
1
+ export const dateISOToString = (
2
+ isoDate: string,
3
+ ): {
4
+ value: string;
5
+ i18n: 'minutesAgo' | 'hoursAgo' | 'onDate';
6
+ } => {
7
+ const date = new Date(isoDate);
8
+ const time = Date.now() - date.getTime();
9
+
10
+ if (time < 1000 * 60 * 60) {
11
+ // Between one minute and one hour
12
+ return { value: `${Math.floor(time / (1000 * 60))}`, i18n: 'minutesAgo' };
13
+ } else if (time < 1000 * 60 * 60 * 24) {
14
+ // Between one hour and one day
15
+ return { value: `${Math.floor(time / (1000 * 60 * 60))}`, i18n: 'hoursAgo' };
16
+ } else {
17
+ // More than one day
18
+ let day = date.getDate().toString();
19
+ let month = (date.getMonth() + 1).toString();
20
+ const year = date.getFullYear().toString();
21
+
22
+ if (day.length < 2) {
23
+ day = '0' + day;
24
+ }
25
+
26
+ if (month.length < 2) {
27
+ month = '0' + month;
28
+ }
29
+
30
+ return { value: `${day}/${month}/${year}`, i18n: 'onDate' };
31
+ }
32
+ };
@@ -0,0 +1,7 @@
1
+ export * from './NotificationsBell';
2
+ export * from './NotificationsList';
3
+ export * from './PopupNotifications';
4
+ export * from './NotificationsCenterContext';
5
+ export * from './useLiveNotifications';
6
+ export * from './CustomerNotificationCenterClient';
7
+ export type { NotificationCenterClientInterface } from './types';
@@ -0,0 +1,39 @@
1
+ import { NotificationTypes, PublishedNotification } from '@mistertemp/types-agency-notification-center';
2
+
3
+ /**
4
+ * Common interface for notification center clients.
5
+ * Both the agency client (match) and customer client implement this contract.
6
+ */
7
+ export interface NotificationCenterClientInterface {
8
+ getNotificationList(params: {
9
+ userCorrelationId: string;
10
+ agencyId: string;
11
+ token: string;
12
+ queryString?: Record<string, unknown>;
13
+ }): Promise<PublishedNotification<NotificationTypes>[]>;
14
+
15
+ getNotificationStatus(params: {
16
+ userCorrelationId: string;
17
+ agencyId: string;
18
+ token: string;
19
+ }): Promise<{ countUnread: number; countUnseen: number }>;
20
+
21
+ markNotificationAsRead(params: {
22
+ userCorrelationId: string;
23
+ agencyId: string;
24
+ notificationId: string;
25
+ token: string;
26
+ }): Promise<unknown>;
27
+
28
+ markAllNotificationAsRead(params: {
29
+ userCorrelationId: string;
30
+ agencyId: string;
31
+ token: string;
32
+ }): Promise<unknown>;
33
+
34
+ markAlertAsOpen(params: {
35
+ userCorrelationId: string;
36
+ agencyId: string;
37
+ token: string;
38
+ }): Promise<unknown>;
39
+ }
@@ -0,0 +1,122 @@
1
+ import { IOTMessageHandler, OAuthContext, useIOTSubscriptions } from '@mistertemp/front-core';
2
+ import {
3
+ NotificationTypes,
4
+ PublishedNotification,
5
+ } from '@mistertemp/types-agency-notification-center';
6
+ import { useCallback, useContext } from 'react';
7
+
8
+ import { NotificationsCenterContext } from './NotificationsCenterContext';
9
+
10
+ interface UseLiveNotificationsOptions {
11
+ /**
12
+ * The path to the notifications page.
13
+ * When the user is on this page, popup notifications are not shown.
14
+ */
15
+ notificationsPagePath?: string;
16
+ }
17
+
18
+ /**
19
+ * Hook that subscribes to real-time notifications via IOT.
20
+ *
21
+ * This hook handles:
22
+ * - Subscribing to user-specific notification topics
23
+ * - Processing incoming notifications
24
+ * - Updating notification counters (unread, unseen)
25
+ * - Adding new notifications to the notification center
26
+ * - Showing popup notifications for urgent ones
27
+ */
28
+ export function useLiveNotifications(options: UseLiveNotificationsOptions = {}) {
29
+ const { notificationsPagePath } = options;
30
+
31
+ const {
32
+ addNotification,
33
+ addPopupNotification,
34
+ isPopoverOpen,
35
+ setUnseenNotifications,
36
+ setUnreadNotifications,
37
+ notificationsHasBeenLoaded,
38
+ agencyId,
39
+ notifications,
40
+ } = useContext(NotificationsCenterContext);
41
+
42
+ const { decodedIdToken } = useContext(OAuthContext);
43
+
44
+ /**
45
+ * Counts notifications that match a specific condition
46
+ */
47
+ function countNotifications(
48
+ notifications: PublishedNotification<NotificationTypes>[],
49
+ condition: (notification: PublishedNotification<NotificationTypes>) => boolean,
50
+ ): number {
51
+ return notifications.reduce(
52
+ (acc, notification) => (condition(notification) ? acc + 1 : acc),
53
+ 0,
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Handler for incoming notifications
59
+ */
60
+ const onNotificationReceived: IOTMessageHandler<PublishedNotification<NotificationTypes>> =
61
+ useCallback(
62
+ ({ detail: { new: notification } }) => {
63
+ if (notification.agencyId !== agencyId) {
64
+ return;
65
+ }
66
+
67
+ // Show popup for urgent notifications when not on the notifications page
68
+ if (notification.isUrgent) {
69
+ const isOnNotificationsPage =
70
+ notificationsPagePath &&
71
+ window.location.pathname === notificationsPagePath;
72
+
73
+ if (!isOnNotificationsPage) {
74
+ addPopupNotification(notification);
75
+ }
76
+ }
77
+
78
+ const newNotifications = [
79
+ notification,
80
+ ...notifications.filter(
81
+ (n) => n.notificationId !== notification.notificationId,
82
+ ),
83
+ ];
84
+
85
+ // Append notifications only when the initial load is done
86
+ if (notificationsHasBeenLoaded) {
87
+ addNotification(notification);
88
+ }
89
+
90
+ // Increment unread notifications
91
+ setUnreadNotifications(
92
+ countNotifications(newNotifications, (notif) => !notif.readAt),
93
+ );
94
+
95
+ // Increment unseen notifications if the popover is closed
96
+ if (!isPopoverOpen) {
97
+ setUnseenNotifications(
98
+ countNotifications(newNotifications, (notif) => !notif.seenAt),
99
+ );
100
+ }
101
+ },
102
+ [
103
+ addNotification,
104
+ addPopupNotification,
105
+ agencyId,
106
+ isPopoverOpen,
107
+ notificationsHasBeenLoaded,
108
+ setUnreadNotifications,
109
+ setUnseenNotifications,
110
+ notifications,
111
+ notificationsPagePath,
112
+ ],
113
+ );
114
+
115
+ // Create topic string for user-specific notifications
116
+ const topic = `userCorrelationId/${decodedIdToken?.['custom:userCorrelationId']}/notifications`;
117
+
118
+ // Subscribe to the notification topic
119
+ useIOTSubscriptions({
120
+ [topic]: onNotificationReceived,
121
+ });
122
+ }
@@ -43,14 +43,7 @@ export type TextFilterProps = Omit<
43
43
  >;
44
44
  inputProps?: Omit<
45
45
  InputTextProps,
46
- | 'placeholder'
47
- | 'data-testid'
48
- | 'value'
49
- | 'size'
50
- | 'type'
51
- | 'onSubmit'
52
- | 'onChange'
53
- | 'inputType'
46
+ 'data-testid' | 'value' | 'size' | 'type' | 'onSubmit' | 'onChange' | 'inputType'
54
47
  >;
55
48
  };
56
49
 
@@ -10,5 +10,6 @@ export * from './CopyToClipboard';
10
10
  export * from './DateFilter';
11
11
  export * from './EnvironmentFilter';
12
12
  export * from './Filter';
13
+ export * from './Notifications';
13
14
  export * from './ProfessionFilter';
14
15
  export * from './TextFilter';
@@ -0,0 +1,11 @@
1
+ {
2
+ "modes": {
3
+ "single": "Día",
4
+ "multiple": "Días",
5
+ "range": "Periodo"
6
+ },
7
+ "actions": {
8
+ "validate": "Aceptar",
9
+ "cancel": "Cancelar"
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "common": {
3
+ "userNotAuthenticated": "No has iniciado sesión."
4
+ },
5
+ "notifications": {
6
+ "markNotificationAsRead": "Se ha producido un error al marcar tu notificación como leída.",
7
+ "markAllNotificationAsRead": "Se ha producido un error al marcar tus notificaciones como leídas.",
8
+ "getNotificationList": "Se ha producido un error al recuperar tus notificaciones.",
9
+ "getNotificationStatus": "Se ha producido un error al recuperar el estado de tus notificaciones.",
10
+ "markAlertAsOpen": "Se ha producido un error al marcar tus notificaciones como abiertas."
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "dateISOToString": {
3
+ "minutesAgo": "Hace {{ value }} min",
4
+ "hoursAgo": "Hace {{ value }} h",
5
+ "onDate": "El {{ value }}"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "notificationsPopover": {
3
+ "title": "Notificaciones",
4
+ "all": "Todas",
5
+ "unread": "No leídas",
6
+ "read": "Leídas",
7
+ "markAllAsRead": "Marcar todo como leído",
8
+ "noNotifications": "No hay notificaciones disponibles.",
9
+ "lastActivityWillDisplayHere": "Las últimas actividades de tus candidatos y clientes aparecerán aquí.",
10
+ "showMore": "Ver más",
11
+ "showLess": "Ver menos"
12
+ },
13
+ "notificationsExtended": {
14
+ "date": "Fecha",
15
+ "reinitFilters": "Restablecer"
16
+ }
17
+ }