@hed-hog/tag 0.0.304 → 0.0.306

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.
@@ -47,12 +47,16 @@ import {
47
47
  SheetTitle,
48
48
  } from '@/components/ui/sheet';
49
49
  import { useDebounce } from '@/hooks/use-debounce';
50
+ import { useFormDraft } from '@/hooks/use-form-draft';
51
+ import { formatDateTime } from '@/lib/format-date';
50
52
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
51
53
  import { zodResolver } from '@hookform/resolvers/zod';
54
+ import { formatDistanceToNow } from 'date-fns';
55
+ import { enUS, ptBR } from 'date-fns/locale';
52
56
  import { Edit, Save, Tag as TagIcon, Trash2 } from 'lucide-react';
53
57
  import { useTranslations } from 'next-intl';
54
58
  import { useEffect, useMemo, useState } from 'react';
55
- import { useForm } from 'react-hook-form';
59
+ import { useForm, useWatch } from 'react-hook-form';
56
60
  import { toast } from 'sonner';
57
61
  import { z } from 'zod';
58
62
 
@@ -70,6 +74,23 @@ type Tag = {
70
74
  status: 'active' | 'inactive';
71
75
  };
72
76
 
77
+ type TagStats = {
78
+ total: number;
79
+ active: number;
80
+ inactive: number;
81
+ };
82
+
83
+ type TagDraftPayload = {
84
+ mode: 'create' | 'edit';
85
+ tagId: number | null;
86
+ values: {
87
+ slug: string;
88
+ color: string;
89
+ status: 'active' | 'inactive';
90
+ };
91
+ };
92
+
93
+ const TAG_FORM_DRAFT_STORAGE_KEY = 'tag-form-draft';
73
94
  const PAGE_SIZE_OPTIONS: number[] = [10, 20, 30, 40, 50];
74
95
 
75
96
  export default function TagPage() {
@@ -94,7 +115,7 @@ export default function TagPage() {
94
115
  const debouncedSearch = useDebounce(searchTerm);
95
116
  const [page, setPage] = useState(1);
96
117
  const [pageSize, setPageSize] = useState<number>(10);
97
- const { request } = useApp();
118
+ const { request, currentLocaleCode, getSettingValue } = useApp();
98
119
 
99
120
  const form = useForm<FormValues>({
100
121
  resolver: zodResolver(formSchema),
@@ -105,6 +126,62 @@ export default function TagPage() {
105
126
  },
106
127
  });
107
128
 
129
+ const watchedTagValues = useWatch({
130
+ control: form.control,
131
+ });
132
+ const hasDraftContent = Boolean(
133
+ (watchedTagValues.slug ?? '').trim() ||
134
+ watchedTagValues.color !== '#000000' ||
135
+ watchedTagValues.status !== 'active'
136
+ );
137
+
138
+ const {
139
+ clearDraft,
140
+ loadDraft,
141
+ hasDraft,
142
+ savedAt: draftSavedAt,
143
+ } = useFormDraft<TagDraftPayload>({
144
+ storageKey: TAG_FORM_DRAFT_STORAGE_KEY,
145
+ value: {
146
+ mode: editingTagId ? 'edit' : 'create',
147
+ tagId: editingTagId,
148
+ values: {
149
+ slug: watchedTagValues.slug ?? '',
150
+ color: watchedTagValues.color ?? '#000000',
151
+ status: watchedTagValues.status ?? 'active',
152
+ },
153
+ },
154
+ hasData: hasDraftContent,
155
+ enabled: isSheetOpen,
156
+ });
157
+
158
+ const draftStatusContent = useMemo(() => {
159
+ if (!hasDraft || !draftSavedAt) {
160
+ return null;
161
+ }
162
+
163
+ const savedDate = new Date(draftSavedAt);
164
+
165
+ if (Number.isNaN(savedDate.getTime())) {
166
+ return null;
167
+ }
168
+
169
+ const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
170
+ const relativeLabel = formatDistanceToNow(savedDate, {
171
+ addSuffix: true,
172
+ locale,
173
+ });
174
+ const absoluteLabel = formatDateTime(
175
+ savedDate,
176
+ getSettingValue,
177
+ currentLocaleCode
178
+ );
179
+
180
+ return currentLocaleCode.startsWith('pt')
181
+ ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
182
+ : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
183
+ }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
184
+
108
185
  const { data: tagResult, refetch: refetchTag } = useQuery<
109
186
  PaginationResult<Tag>
110
187
  >({
@@ -123,22 +200,28 @@ export default function TagPage() {
123
200
  });
124
201
  const { data = [], total = 0 } = tagResult ?? {};
125
202
 
126
- const { data: statsData, refetch: refetchStats } = useQuery<any>({
203
+ const { data: statsData, refetch: refetchStats } = useQuery<TagStats>({
127
204
  queryKey: ['tag-stats'],
128
205
  queryFn: async () => {
129
- const response = await request({
206
+ const response = await request<TagStats>({
130
207
  url: '/tag/stats',
131
208
  });
132
- return response.data;
209
+ return response.data as TagStats;
133
210
  },
134
211
  });
135
212
 
136
213
  const handleNewTag = (): void => {
137
- form.reset({
138
- slug: '',
139
- color: '#000000',
140
- status: 'active',
141
- });
214
+ const storedDraft = loadDraft();
215
+
216
+ form.reset(
217
+ storedDraft?.payload.mode === 'create'
218
+ ? storedDraft.payload.values
219
+ : {
220
+ slug: '',
221
+ color: '#000000',
222
+ status: 'active',
223
+ }
224
+ );
142
225
  setEditingTagId(null);
143
226
  setIsSheetOpen(true);
144
227
  };
@@ -150,11 +233,20 @@ export default function TagPage() {
150
233
  method: 'GET',
151
234
  });
152
235
 
153
- form.reset({
154
- slug: response.data.slug ?? '',
155
- color: response.data.color ?? '#000000',
156
- status: response.data.status ?? 'active',
157
- });
236
+ const storedDraft = loadDraft();
237
+ const shouldRestoreDraft =
238
+ storedDraft?.payload.mode === 'edit' &&
239
+ storedDraft.payload.tagId === tag.id;
240
+
241
+ form.reset(
242
+ shouldRestoreDraft
243
+ ? storedDraft.payload.values
244
+ : {
245
+ slug: response.data.slug ?? '',
246
+ color: response.data.color ?? '#000000',
247
+ status: response.data.status ?? 'active',
248
+ }
249
+ );
158
250
  setEditingTagId(tag.id);
159
251
  setIsSheetOpen(true);
160
252
  } catch (error) {
@@ -187,7 +279,9 @@ export default function TagPage() {
187
279
  toast.success(t('successCreate'));
188
280
  }
189
281
 
282
+ clearDraft();
190
283
  setIsSheetOpen(false);
284
+ setEditingTagId(null);
191
285
  await refetchTag();
192
286
  await refetchStats();
193
287
  } catch (error) {
@@ -456,8 +550,12 @@ export default function TagPage() {
456
550
 
457
551
  <FormActions
458
552
  sheet
553
+ statusContent={draftStatusContent}
459
554
  cancelLabel={t('cancel')}
460
- onCancel={() => setIsSheetOpen(false)}
555
+ onCancel={() => {
556
+ setIsSheetOpen(false);
557
+ setEditingTagId(null);
558
+ }}
461
559
  submitIcon={<Save className="size-4" />}
462
560
  submitLabel={t('save')}
463
561
  submitType="submit"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/tag",
3
- "version": "0.0.304",
3
+ "version": "0.0.306",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,11 +9,11 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
+ "@hed-hog/api": "0.0.6",
12
13
  "@hed-hog/api-pagination": "0.0.7",
14
+ "@hed-hog/core": "0.0.306",
13
15
  "@hed-hog/api-prisma": "0.0.6",
14
- "@hed-hog/api": "0.0.6",
15
- "@hed-hog/api-locale": "0.0.14",
16
- "@hed-hog/core": "0.0.304"
16
+ "@hed-hog/api-locale": "0.0.14"
17
17
  },
18
18
  "exports": {
19
19
  ".": {