@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.
- package/cli/dist/index.js +1 -1
- package/package.json +1 -1
- package/registry.json +0 -230
- package/src/components/advance-invoices/advance-invoices.hooks.ts +2 -2
- package/src/components/documents/documents.hooks.ts +5 -48
- package/src/components/documents/shared/document-preview-display.tsx +12 -1
- package/src/components/documents/view/document-actions-bar.tsx +20 -12
- package/src/components/documents/view/document-activities-list.tsx +166 -0
- package/src/components/documents/view/document-details-card.tsx +6 -6
- package/src/components/documents/view/index.ts +1 -0
- package/src/components/documents/view/locales/de.ts +32 -0
- package/src/components/documents/view/locales/es.ts +32 -0
- package/src/components/documents/view/locales/fr.ts +32 -0
- package/src/components/documents/view/locales/hr.ts +32 -0
- package/src/components/documents/view/locales/it.ts +32 -0
- package/src/components/documents/view/locales/nl.ts +32 -0
- package/src/components/documents/view/locales/pl.ts +32 -0
- package/src/components/documents/view/locales/pt.ts +32 -0
- package/src/components/documents/view/locales/sl.ts +32 -0
- package/src/components/entities/fina-settings-form/locales/de.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/en.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/es.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/fr.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/hr.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/it.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/nl.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/pl.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/pt.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/sl.ts +3 -0
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +15 -7
- package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +1 -1
- package/src/components/entities/furs-settings-form/locales/de.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/en.ts +12 -0
- package/src/components/entities/furs-settings-form/locales/es.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/fr.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/hr.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/it.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/nl.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/pl.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/pt.ts +2 -0
- package/src/components/entities/furs-settings-form/locales/sl.ts +14 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +121 -1
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +1 -0
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +44 -32
- package/src/components/invoices/index.ts +1 -1
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +2 -2
- package/src/components/invoices/view/fiscalization-status-card.tsx +121 -0
- package/src/generate-schemas.ts +13 -1
- package/src/generated/schemas/advanceinvoice.ts +79 -187
- package/src/generated/schemas/creditnote.ts +63 -86
- package/src/generated/schemas/customadvanceinvoice.ts +70 -97
- package/src/generated/schemas/customcreditnote.ts +70 -97
- package/src/generated/schemas/customestimate.ts +68 -97
- package/src/generated/schemas/custominvoice.ts +70 -97
- package/src/generated/schemas/estimate.ts +67 -172
- package/src/generated/schemas/invoice.ts +79 -187
- package/src/generated/schemas/registerfursrealestatepremise_body.ts +11 -7
- package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +61 -157
- package/src/generated/schemas/rendercreditnotepreview_body.ts +61 -157
- package/src/generated/schemas/renderestimatepreview_body.ts +61 -157
- package/src/generated/schemas/renderinvoicepreview_body.ts +61 -157
- package/src/hooks/use-duplicate-document.ts +19 -11
- package/src/providers/entities-provider.tsx +21 -0
- package/src/components/invoices/view/fina-info-display.tsx +0 -196
- 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")}
|
|
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("
|
|
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")}
|
|
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("
|
|
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")}
|
|
247
|
+
<FormLabel>{t("Street")} *</FormLabel>
|
|
237
248
|
<FormControl>
|
|
238
|
-
<Input placeholder="Dunajska cesta" {...field}
|
|
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")}
|
|
262
|
+
<FormLabel>{t("House Number")} *</FormLabel>
|
|
252
263
|
<FormControl>
|
|
253
|
-
<Input placeholder="22" {...field}
|
|
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")}
|
|
291
|
+
<FormLabel>{t("City")} *</FormLabel>
|
|
281
292
|
<FormControl>
|
|
282
|
-
<Input placeholder="Ljubljana" {...field}
|
|
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")}
|
|
304
|
+
<FormLabel>{t("Postal Code")} *</FormLabel>
|
|
294
305
|
<FormControl>
|
|
295
|
-
<Input placeholder="1000" {...field}
|
|
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 {
|
|
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-
|
|
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={
|
|
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} ·{" "}
|
|
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
|
+
}
|
package/src/generate-schemas.ts
CHANGED
|
@@ -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
|
-
|
|
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'.
|