@spaceinvoices/react-ui 0.3.0 → 0.4.1

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 (65) hide show
  1. package/cli/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/registry.json +0 -230
  4. package/src/components/advance-invoices/advance-invoices.hooks.ts +2 -2
  5. package/src/components/documents/documents.hooks.ts +5 -48
  6. package/src/components/documents/shared/document-preview-display.tsx +12 -1
  7. package/src/components/documents/view/document-actions-bar.tsx +20 -12
  8. package/src/components/documents/view/document-activities-list.tsx +166 -0
  9. package/src/components/documents/view/document-details-card.tsx +6 -6
  10. package/src/components/documents/view/index.ts +1 -0
  11. package/src/components/documents/view/locales/de.ts +32 -0
  12. package/src/components/documents/view/locales/es.ts +32 -0
  13. package/src/components/documents/view/locales/fr.ts +32 -0
  14. package/src/components/documents/view/locales/hr.ts +32 -0
  15. package/src/components/documents/view/locales/it.ts +32 -0
  16. package/src/components/documents/view/locales/nl.ts +32 -0
  17. package/src/components/documents/view/locales/pl.ts +32 -0
  18. package/src/components/documents/view/locales/pt.ts +32 -0
  19. package/src/components/documents/view/locales/sl.ts +32 -0
  20. package/src/components/entities/fina-settings-form/locales/de.ts +3 -0
  21. package/src/components/entities/fina-settings-form/locales/en.ts +3 -0
  22. package/src/components/entities/fina-settings-form/locales/es.ts +3 -0
  23. package/src/components/entities/fina-settings-form/locales/fr.ts +3 -0
  24. package/src/components/entities/fina-settings-form/locales/hr.ts +3 -0
  25. package/src/components/entities/fina-settings-form/locales/it.ts +3 -0
  26. package/src/components/entities/fina-settings-form/locales/nl.ts +3 -0
  27. package/src/components/entities/fina-settings-form/locales/pl.ts +3 -0
  28. package/src/components/entities/fina-settings-form/locales/pt.ts +3 -0
  29. package/src/components/entities/fina-settings-form/locales/sl.ts +3 -0
  30. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +15 -7
  31. package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +1 -1
  32. package/src/components/entities/furs-settings-form/locales/de.ts +2 -0
  33. package/src/components/entities/furs-settings-form/locales/en.ts +12 -0
  34. package/src/components/entities/furs-settings-form/locales/es.ts +2 -0
  35. package/src/components/entities/furs-settings-form/locales/fr.ts +2 -0
  36. package/src/components/entities/furs-settings-form/locales/hr.ts +2 -0
  37. package/src/components/entities/furs-settings-form/locales/it.ts +2 -0
  38. package/src/components/entities/furs-settings-form/locales/nl.ts +2 -0
  39. package/src/components/entities/furs-settings-form/locales/pl.ts +2 -0
  40. package/src/components/entities/furs-settings-form/locales/pt.ts +2 -0
  41. package/src/components/entities/furs-settings-form/locales/sl.ts +14 -0
  42. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +121 -1
  43. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +1 -0
  44. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +44 -32
  45. package/src/components/invoices/index.ts +1 -1
  46. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +2 -2
  47. package/src/components/invoices/view/fiscalization-status-card.tsx +121 -0
  48. package/src/generate-schemas.ts +13 -1
  49. package/src/generated/schemas/advanceinvoice.ts +79 -187
  50. package/src/generated/schemas/creditnote.ts +63 -86
  51. package/src/generated/schemas/customadvanceinvoice.ts +70 -97
  52. package/src/generated/schemas/customcreditnote.ts +70 -97
  53. package/src/generated/schemas/customestimate.ts +68 -97
  54. package/src/generated/schemas/custominvoice.ts +70 -97
  55. package/src/generated/schemas/estimate.ts +67 -172
  56. package/src/generated/schemas/invoice.ts +79 -187
  57. package/src/generated/schemas/registerfursrealestatepremise_body.ts +11 -7
  58. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +61 -157
  59. package/src/generated/schemas/rendercreditnotepreview_body.ts +61 -157
  60. package/src/generated/schemas/renderestimatepreview_body.ts +61 -157
  61. package/src/generated/schemas/renderinvoicepreview_body.ts +61 -157
  62. package/src/hooks/use-duplicate-document.ts +19 -11
  63. package/src/providers/entities-provider.tsx +21 -0
  64. package/src/components/invoices/view/fina-info-display.tsx +0 -196
  65. package/src/components/invoices/view/furs-info-display.tsx +0 -213
@@ -1,7 +1,9 @@
1
1
  import type { Entity } from "@spaceinvoices/js-sdk";
2
- import { ChevronRight, Settings, User } from "lucide-react";
2
+ import { AlertTriangle, Building2, ChevronRight, Settings, User } from "lucide-react";
3
3
  import { type FC, type ReactNode, useEffect, useState } from "react";
4
4
  import type { UseFormReturn } from "react-hook-form";
5
+ import { useUpdateEntity } from "@/ui/components/entities/entities.hooks";
6
+ import { Alert, AlertDescription } from "@/ui/components/ui/alert";
5
7
  import { Button } from "@/ui/components/ui/button";
6
8
  import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/ui/components/ui/form";
7
9
  import { Input } from "@/ui/components/ui/input";
@@ -30,6 +32,37 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
30
32
  const wrap = (section: SectionType, content: ReactNode) => (wrapSection ? wrapSection(section, content) : content);
31
33
  const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
32
34
 
35
+ // Entity info (local state for form)
36
+ const [entityTaxNumber, setEntityTaxNumber] = useState("");
37
+ const [entityAddress, setEntityAddress] = useState("");
38
+ const [entityCity, setEntityCity] = useState("");
39
+ const [entityPostCode, setEntityPostCode] = useState("");
40
+
41
+ // Initialize entity fields from entity prop
42
+ useEffect(() => {
43
+ setEntityTaxNumber(entity.tax_number || "");
44
+ setEntityAddress(entity.address || "");
45
+ setEntityCity(entity.city || "");
46
+ setEntityPostCode(entity.post_code || "");
47
+ }, [entity.tax_number, entity.address, entity.city, entity.post_code]);
48
+
49
+ const { mutate: updateEntity, isPending: isEntityUpdatePending } = useUpdateEntity({
50
+ onSuccess: () => onSuccess?.(),
51
+ onError: (error) => onError?.(error),
52
+ });
53
+
54
+ const handleSaveEntityInfo = () => {
55
+ updateEntity({
56
+ id: entity.id,
57
+ data: {
58
+ tax_number: entityTaxNumber || null,
59
+ address: entityAddress || null,
60
+ city: entityCity || null,
61
+ post_code: entityPostCode || null,
62
+ },
63
+ });
64
+ };
65
+
33
66
  // User operator settings (local state for form)
34
67
  const { data: userFursSettings, isLoading: userSettingsLoading } = useUserFursSettings(entity.id);
35
68
  const [operatorTaxNumber, setOperatorTaxNumber] = useState("");
@@ -58,6 +91,92 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
58
91
  });
59
92
  };
60
93
 
94
+ // Entity Information content
95
+ const entityInfoContent = (
96
+ <div className="space-y-4">
97
+ <div className="flex items-center gap-3">
98
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-orange-500/10">
99
+ <Building2 className="h-5 w-5 text-orange-600 dark:text-orange-400" />
100
+ </div>
101
+ <div>
102
+ <h3 className="font-semibold text-lg">{t("Entity Information")}</h3>
103
+ <p className="text-muted-foreground text-sm">{t("Required company details for FURS fiscalization")}</p>
104
+ </div>
105
+ </div>
106
+
107
+ {!entity.tax_number && (
108
+ <Alert variant="destructive">
109
+ <AlertTriangle className="h-4 w-4" />
110
+ <AlertDescription>{t("Tax number is required for FURS fiscalization")}</AlertDescription>
111
+ </Alert>
112
+ )}
113
+
114
+ <div className="space-y-4">
115
+ <div>
116
+ <label htmlFor="entity-tax-number" className="font-medium text-sm">
117
+ {t("Entity Tax Number")}
118
+ </label>
119
+ <Input
120
+ id="entity-tax-number"
121
+ placeholder="12345678"
122
+ value={entityTaxNumber}
123
+ onChange={(e) => setEntityTaxNumber(e.target.value)}
124
+ className={cn("mt-2 h-10", !entity.tax_number && "border-destructive")}
125
+ />
126
+ <p className="mt-1 text-muted-foreground text-xs">
127
+ {t("Your company's tax number (must match FURS certificate)")}
128
+ </p>
129
+ </div>
130
+
131
+ <div>
132
+ <label htmlFor="entity-address" className="font-medium text-sm">
133
+ {t("Address")}
134
+ </label>
135
+ <Input
136
+ id="entity-address"
137
+ value={entityAddress}
138
+ onChange={(e) => setEntityAddress(e.target.value)}
139
+ className="mt-2 h-10"
140
+ />
141
+ </div>
142
+
143
+ <div className="grid grid-cols-2 gap-4">
144
+ <div>
145
+ <label htmlFor="entity-post-code" className="font-medium text-sm">
146
+ {t("Post Code")}
147
+ </label>
148
+ <Input
149
+ id="entity-post-code"
150
+ value={entityPostCode}
151
+ onChange={(e) => setEntityPostCode(e.target.value)}
152
+ className="mt-2 h-10"
153
+ />
154
+ </div>
155
+ <div>
156
+ <label htmlFor="entity-city" className="font-medium text-sm">
157
+ {t("City")}
158
+ </label>
159
+ <Input
160
+ id="entity-city"
161
+ value={entityCity}
162
+ onChange={(e) => setEntityCity(e.target.value)}
163
+ className="mt-2 h-10"
164
+ />
165
+ </div>
166
+ </div>
167
+
168
+ <Button
169
+ type="button"
170
+ onClick={handleSaveEntityInfo}
171
+ disabled={isEntityUpdatePending}
172
+ className="cursor-pointer"
173
+ >
174
+ {isEntityUpdatePending ? t("Saving...") : t("Save Entity Info")}
175
+ </Button>
176
+ </div>
177
+ </div>
178
+ );
179
+
61
180
  // Operator Settings content
62
181
  const operatorContent = (
63
182
  <div className="space-y-4">
@@ -244,6 +363,7 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
244
363
 
245
364
  return (
246
365
  <div className="space-y-6">
366
+ {wrap("entity-info", entityInfoContent)}
247
367
  {wrap("operator", operatorContent)}
248
368
  {wrap("fiscalization", fiscalizationContent)}
249
369
  {wrap("advanced", advancedContent)}
@@ -307,6 +307,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
307
307
  entity={entity}
308
308
  type={registerType}
309
309
  t={t}
310
+ existingPremiseNames={premises.map((p) => p.business_premise_name)}
310
311
  onSuccess={() => {
311
312
  setRegisterDialogOpen(false);
312
313
  onSuccess?.();
@@ -41,6 +41,7 @@ interface RegisterPremiseDialogProps {
41
41
  entity: Entity;
42
42
  type: "real-estate" | "movable";
43
43
  t: (key: string) => string;
44
+ existingPremiseNames?: string[];
44
45
  onSuccess?: () => void;
45
46
  onError?: (error: unknown) => void;
46
47
  }
@@ -51,6 +52,7 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
51
52
  entity,
52
53
  type,
53
54
  t,
55
+ existingPremiseNames = [],
54
56
  onSuccess,
55
57
  onError,
56
58
  }) => {
@@ -63,8 +65,8 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
63
65
  business_premise_name: "",
64
66
  real_estate: {
65
67
  cadastral_number: "",
66
- building_number: "",
67
- building_section: "",
68
+ building_number: "0",
69
+ building_section: "0",
68
70
  community: "",
69
71
  city: "",
70
72
  street: "",
@@ -86,12 +88,21 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
86
88
  },
87
89
  });
88
90
 
91
+ const handleMutationError = (error: unknown, form: typeof realEstateForm | typeof movableForm) => {
92
+ const err = error as { status?: number; data?: { message?: string } };
93
+ if (err?.status === 409 && err?.data?.message) {
94
+ form.setError("business_premise_name", { message: err.data.message });
95
+ } else {
96
+ onError?.(error);
97
+ }
98
+ };
99
+
89
100
  const { mutate: registerRealEstate, isPending: isRealEstatePending } = useRegisterRealEstatePremise({
90
101
  onSuccess: () => {
91
102
  realEstateForm.reset();
92
103
  onSuccess?.();
93
104
  },
94
- onError,
105
+ onError: (error) => handleMutationError(error, realEstateForm),
95
106
  });
96
107
 
97
108
  const { mutate: registerMovable, isPending: isMovablePending } = useRegisterMovablePremise({
@@ -99,10 +110,18 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
99
110
  movableForm.reset();
100
111
  onSuccess?.();
101
112
  },
102
- onError,
113
+ onError: (error) => handleMutationError(error, movableForm),
103
114
  });
104
115
 
116
+ const isDuplicateName = (name: string) => existingPremiseNames.some((n) => n.toUpperCase() === name.toUpperCase());
117
+
105
118
  const handleRealEstateSubmit = (data: RealEstatePremiseForm) => {
119
+ if (isDuplicateName(data.business_premise_name)) {
120
+ realEstateForm.setError("business_premise_name", {
121
+ message: t("A premise with this name already exists"),
122
+ });
123
+ return;
124
+ }
106
125
  registerRealEstate({
107
126
  entityId: entity.id,
108
127
  data,
@@ -110,6 +129,12 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
110
129
  };
111
130
 
112
131
  const handleMovableSubmit = (data: MovablePremiseForm) => {
132
+ if (isDuplicateName(data.business_premise_name)) {
133
+ movableForm.setError("business_premise_name", {
134
+ message: t("A premise with this name already exists"),
135
+ });
136
+ return;
137
+ }
113
138
  registerMovable({
114
139
  entityId: entity.id,
115
140
  data,
@@ -172,18 +197,11 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
172
197
  name="real_estate.building_number"
173
198
  render={({ field }) => (
174
199
  <FormItem>
175
- <FormLabel>{t("Building Number")}</FormLabel>
200
+ <FormLabel>{t("Building Number")} *</FormLabel>
176
201
  <FormControl>
177
- <Input
178
- type="text"
179
- inputMode="numeric"
180
- pattern="[0-9]*"
181
- placeholder="456"
182
- {...field}
183
- value={field.value || ""}
184
- />
202
+ <Input type="text" inputMode="numeric" pattern="[0-9]*" placeholder="456" {...field} />
185
203
  </FormControl>
186
- <FormDescription className="text-xs">{t("Must be numeric (optional)")}</FormDescription>
204
+ <FormDescription className="text-xs">{t("Numeric, use 0 if not applicable")}</FormDescription>
187
205
  <FormMessage />
188
206
  </FormItem>
189
207
  )}
@@ -193,18 +211,11 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
193
211
  name="real_estate.building_section"
194
212
  render={({ field }) => (
195
213
  <FormItem>
196
- <FormLabel>{t("Building Section")}</FormLabel>
214
+ <FormLabel>{t("Building Section")} *</FormLabel>
197
215
  <FormControl>
198
- <Input
199
- type="text"
200
- inputMode="numeric"
201
- pattern="[0-9]*"
202
- placeholder="1"
203
- {...field}
204
- value={field.value || ""}
205
- />
216
+ <Input type="text" inputMode="numeric" pattern="[0-9]*" placeholder="1" {...field} />
206
217
  </FormControl>
207
- <FormDescription className="text-xs">{t("Must be numeric (optional)")}</FormDescription>
218
+ <FormDescription className="text-xs">{t("Numeric, use 0 if not applicable")}</FormDescription>
208
219
  <FormMessage />
209
220
  </FormItem>
210
221
  )}
@@ -233,9 +244,9 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
233
244
  name="real_estate.street"
234
245
  render={({ field }) => (
235
246
  <FormItem>
236
- <FormLabel>{t("Street")}</FormLabel>
247
+ <FormLabel>{t("Street")} *</FormLabel>
237
248
  <FormControl>
238
- <Input placeholder="Dunajska cesta" {...field} value={field.value || ""} />
249
+ <Input placeholder="Dunajska cesta" {...field} />
239
250
  </FormControl>
240
251
  <FormMessage />
241
252
  </FormItem>
@@ -248,9 +259,9 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
248
259
  name="real_estate.house_number"
249
260
  render={({ field }) => (
250
261
  <FormItem>
251
- <FormLabel>{t("House Number")}</FormLabel>
262
+ <FormLabel>{t("House Number")} *</FormLabel>
252
263
  <FormControl>
253
- <Input placeholder="22" {...field} value={field.value || ""} />
264
+ <Input placeholder="22" {...field} />
254
265
  </FormControl>
255
266
  <FormMessage />
256
267
  </FormItem>
@@ -277,9 +288,9 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
277
288
  name="real_estate.city"
278
289
  render={({ field }) => (
279
290
  <FormItem>
280
- <FormLabel>{t("City")}</FormLabel>
291
+ <FormLabel>{t("City")} *</FormLabel>
281
292
  <FormControl>
282
- <Input placeholder="Ljubljana" {...field} value={field.value || ""} />
293
+ <Input placeholder="Ljubljana" {...field} />
283
294
  </FormControl>
284
295
  <FormMessage />
285
296
  </FormItem>
@@ -290,10 +301,11 @@ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
290
301
  name="real_estate.postal_code"
291
302
  render={({ field }) => (
292
303
  <FormItem>
293
- <FormLabel>{t("Postal Code")}</FormLabel>
304
+ <FormLabel>{t("Postal Code")} *</FormLabel>
294
305
  <FormControl>
295
- <Input placeholder="1000" {...field} value={field.value || ""} />
306
+ <Input placeholder="1000" {...field} />
296
307
  </FormControl>
308
+ <FormDescription className="text-xs">{t("Exactly 4 digits")}</FormDescription>
297
309
  <FormMessage />
298
310
  </FormItem>
299
311
  )}
@@ -5,5 +5,5 @@ export * from "./list";
5
5
  export { SendEmailDialog } from "./send-email-dialog";
6
6
  export * from "./shared";
7
7
  export { EslogInfoDisplay } from "./view/eslog-info-display";
8
- export { FursInfoDisplay } from "./view/furs-info-display";
8
+ export { FiscalizationStatusCard } from "./view/fiscalization-status-card";
9
9
  // Note: InvoicePreviewDisplay is now DocumentPreviewDisplay in documents/shared
@@ -195,7 +195,7 @@ export function SendEmailDialog({
195
195
  </Button>
196
196
  </DialogTrigger>
197
197
  )}
198
- <DialogContent className="sm:max-w-md">
198
+ <DialogContent className="sm:max-w-lg">
199
199
  <DialogHeader>
200
200
  <DialogTitle>{t("Send Invoice by Email")}</DialogTitle>
201
201
  <DialogDescription>
@@ -266,7 +266,7 @@ export function SendEmailDialog({
266
266
  document={invoice}
267
267
  disabled={isLoading}
268
268
  multiline
269
- rows={4}
269
+ rows={8}
270
270
  />
271
271
  </FormControl>
272
272
  <FormDescription>{t("Leave empty to use default")}</FormDescription>
@@ -0,0 +1,121 @@
1
+ import type { FinaFiscalizationResponse, FursFiscalizationResponse } from "@spaceinvoices/js-sdk";
2
+ import { AlertCircle, CheckCircle2, Clock, Loader2, MinusCircle, RefreshCw, XCircle } from "lucide-react";
3
+ import { Alert, AlertDescription } from "@/ui/components/ui/alert";
4
+ import { Badge } from "@/ui/components/ui/badge";
5
+ import { Button } from "@/ui/components/ui/button";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "@/ui/components/ui/card";
7
+ import type { ComponentTranslationProps } from "@/ui/lib/translation";
8
+ import { createTranslation } from "@/ui/lib/translation";
9
+ import de from "../../documents/view/locales/de";
10
+ import es from "../../documents/view/locales/es";
11
+ import fr from "../../documents/view/locales/fr";
12
+ import hr from "../../documents/view/locales/hr";
13
+ import it from "../../documents/view/locales/it";
14
+ import nl from "../../documents/view/locales/nl";
15
+ import pl from "../../documents/view/locales/pl";
16
+ import pt from "../../documents/view/locales/pt";
17
+ import sl from "../../documents/view/locales/sl";
18
+
19
+ const translations = { de, es, fr, hr, it, nl, pl, pt, sl } as const;
20
+
21
+ type FiscalizationData = FursFiscalizationResponse | FinaFiscalizationResponse;
22
+
23
+ interface FiscalizationStatusCardProps extends ComponentTranslationProps {
24
+ fiscalizationType: "furs" | "fina";
25
+ fiscalizationData: FiscalizationData | null | undefined;
26
+ onRetry?: () => void;
27
+ isRetrying?: boolean;
28
+ }
29
+
30
+ export function FiscalizationStatusCard({
31
+ fiscalizationType,
32
+ fiscalizationData,
33
+ onRetry,
34
+ isRetrying,
35
+ t: translateFn,
36
+ namespace,
37
+ locale,
38
+ }: FiscalizationStatusCardProps) {
39
+ const t = createTranslation({
40
+ t: translateFn,
41
+ namespace,
42
+ locale,
43
+ translations,
44
+ });
45
+
46
+ if (!fiscalizationData) {
47
+ return null;
48
+ }
49
+
50
+ const label = fiscalizationType === "furs" ? "FURS" : "FINA";
51
+
52
+ const getStatusBadge = () => {
53
+ switch (fiscalizationData.status) {
54
+ case "success":
55
+ return (
56
+ <Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100">
57
+ <CheckCircle2 className="mr-1 h-3 w-3" />
58
+ {t("Fiscalized")}
59
+ </Badge>
60
+ );
61
+ case "pending":
62
+ return (
63
+ <Badge className="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100">
64
+ <Clock className="mr-1 h-3 w-3" />
65
+ {t("Pending")}
66
+ </Badge>
67
+ );
68
+ case "failed":
69
+ return (
70
+ <Badge className="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100">
71
+ <XCircle className="mr-1 h-3 w-3" />
72
+ {t("Failed")}
73
+ </Badge>
74
+ );
75
+ case "skipped":
76
+ return (
77
+ <Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100">
78
+ <MinusCircle className="mr-1 h-3 w-3" />
79
+ {t("Skipped")}
80
+ </Badge>
81
+ );
82
+ default:
83
+ return null;
84
+ }
85
+ };
86
+
87
+ return (
88
+ <Card>
89
+ <CardHeader className="pb-3">
90
+ <CardTitle className="flex items-center justify-between text-lg">
91
+ <span>{t("Fiscalization")}</span>
92
+ {getStatusBadge()}
93
+ </CardTitle>
94
+ </CardHeader>
95
+ <CardContent className="space-y-3">
96
+ <div className="text-muted-foreground text-sm">
97
+ {label} &middot;{" "}
98
+ {fiscalizationData.fiscalized_at && new Date(fiscalizationData.fiscalized_at).toLocaleString(locale)}
99
+ </div>
100
+
101
+ {fiscalizationData.status === "failed" && fiscalizationData.error && (
102
+ <Alert variant="destructive">
103
+ <AlertCircle className="h-4 w-4" />
104
+ <AlertDescription className="text-sm">{fiscalizationData.error}</AlertDescription>
105
+ </Alert>
106
+ )}
107
+
108
+ {fiscalizationData.status === "skipped" && (fiscalizationData as any).data?.reason && (
109
+ <div className="text-muted-foreground text-sm">{(fiscalizationData as any).data.reason}</div>
110
+ )}
111
+
112
+ {fiscalizationData.status === "failed" && onRetry && (
113
+ <Button variant="outline" size="sm" onClick={onRetry} disabled={isRetrying}>
114
+ {isRetrying ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RefreshCw className="mr-2 h-4 w-4" />}
115
+ {t("Retry fiscalization")}
116
+ </Button>
117
+ )}
118
+ </CardContent>
119
+ </Card>
120
+ );
121
+ }
@@ -86,7 +86,8 @@ async function main() {
86
86
  for (const ref of constRefs) {
87
87
  const cleanRef = ref.replace(/\.(optional|nullable)\(\)/, "");
88
88
  // Check if it's actually a schema definition in the file
89
- if (fullContent.includes(`const ${cleanRef} = z.`)) {
89
+ // Handle both `const Foo = z.object(...)` and `const Foo = z\n .object(...)`
90
+ if (fullContent.includes(`const ${cleanRef} = z.`) || fullContent.includes(`const ${cleanRef} = z\n`)) {
90
91
  dependencies.push(cleanRef);
91
92
  }
92
93
  }
@@ -244,6 +245,17 @@ ${exports}
244
245
  await fs.writeFile(path.join(SCHEMAS_DIR, `${groupName}.ts`), schemaContent);
245
246
  }
246
247
 
248
+ // Add manual re-exports that can't be auto-generated
249
+ // Credit note create uses the same body as invoice create
250
+ const creditNoteFile = path.join(SCHEMAS_DIR, "creditnote.ts");
251
+ const creditNoteContent = await fs.readFile(creditNoteFile, "utf-8");
252
+ if (!creditNoteContent.includes("createCreditNoteSchema")) {
253
+ await fs.appendFile(
254
+ creditNoteFile,
255
+ "\n// Re-export invoice create schema as credit note create schema (same body structure)\nexport { createInvoiceSchema as createCreditNoteSchema, type CreateInvoiceSchema as CreateCreditNoteSchema } from './invoice';\n",
256
+ );
257
+ }
258
+
247
259
  // Create index file
248
260
  const indexContent = `/**
249
261
  * This file was automatically generated using 'bun generate-schemas'.