@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
@@ -15,6 +15,56 @@ const LineDiscount = z.object({
15
15
  });
16
16
 
17
17
 
18
+ // Dependency schema for renderinvoicepreview_body
19
+ const DocumentItemTax = z
20
+ .object({
21
+ rate: z.number(),
22
+ tax_id: z.string(),
23
+ classification: z.string(),
24
+ reverse_charge: z.boolean(),
25
+ amount: z.number(),
26
+ })
27
+ .partial();
28
+
29
+
30
+ // Dependency schema for renderinvoicepreview_body
31
+ const DocumentEntity = z
32
+ .object({
33
+ name: z.union([z.string(), z.null()]),
34
+ email: z.union([z.string(), z.null()]),
35
+ address: z.union([z.string(), z.null()]),
36
+ address_2: z.union([z.string(), z.null()]),
37
+ post_code: z.union([z.string(), z.null()]),
38
+ city: z.union([z.string(), z.null()]),
39
+ state: z.union([z.string(), z.null()]),
40
+ country: z.union([z.string(), z.null()]),
41
+ country_code: z.union([z.string(), z.null()]),
42
+ tax_number: z.union([z.string(), z.null()]),
43
+ tax_number_2: z.union([z.string(), z.null()]),
44
+ company_number: z.union([z.string(), z.null()]),
45
+ bank_account: z.union([
46
+ z
47
+ .object({
48
+ type: z
49
+ .enum(["iban", "us_domestic", "uk_domestic", "other"])
50
+ .default("iban"),
51
+ name: z.string(),
52
+ bank_name: z.string(),
53
+ iban: z.string(),
54
+ account_number: z.string(),
55
+ bic: z.string(),
56
+ routing_number: z.string(),
57
+ sort_code: z.string(),
58
+ })
59
+ .partial()
60
+ .passthrough(),
61
+ z.null(),
62
+ ]),
63
+ })
64
+ .partial()
65
+ .passthrough();
66
+
67
+
18
68
  // Dependency schema for renderinvoicepreview_body
19
69
  const CompleteInvoicePreview = z.object({
20
70
  is_draft: z.boolean().optional(),
@@ -22,84 +72,17 @@ const CompleteInvoicePreview = z.object({
22
72
  .string()
23
73
  .regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/)
24
74
  .optional(),
25
- issuer: z
26
- .object({
27
- name: z.union([z.string(), z.null()]),
28
- email: z.union([z.string(), z.null()]),
29
- address: z.union([z.string(), z.null()]),
30
- address_2: z.union([z.string(), z.null()]),
31
- post_code: z.union([z.string(), z.null()]),
32
- city: z.union([z.string(), z.null()]),
33
- state: z.union([z.string(), z.null()]),
34
- country: z.union([z.string(), z.null()]),
35
- country_code: z.union([z.string(), z.null()]),
36
- tax_number: z.union([z.string(), z.null()]),
37
- tax_number_2: z.union([z.string(), z.null()]),
38
- company_number: z.union([z.string(), z.null()]),
39
- bank_account: z.union([
40
- z
41
- .object({
42
- type: z
43
- .enum(["iban", "us_domestic", "uk_domestic", "other"])
44
- .default("iban"),
45
- name: z.string(),
46
- bank_name: z.string(),
47
- iban: z.string(),
48
- account_number: z.string(),
49
- bic: z.string(),
50
- routing_number: z.string(),
51
- sort_code: z.string(),
52
- })
53
- .partial()
54
- .passthrough(),
55
- z.null(),
56
- ]),
57
- })
58
- .partial()
59
- .passthrough()
60
- .optional(),
75
+ issuer: DocumentEntity.optional(),
61
76
  customer_id: z.union([z.string(), z.null()]).optional(),
62
- customer: z
63
- .union([
77
+ customer: DocumentEntity.and(
78
+ z.union([
64
79
  z
65
- .object({
66
- name: z.union([z.string(), z.null()]),
67
- email: z.union([z.string(), z.null()]),
68
- address: z.union([z.string(), z.null()]),
69
- address_2: z.union([z.string(), z.null()]),
70
- post_code: z.union([z.string(), z.null()]),
71
- city: z.union([z.string(), z.null()]),
72
- state: z.union([z.string(), z.null()]),
73
- country: z.union([z.string(), z.null()]),
74
- country_code: z.union([z.string(), z.null()]),
75
- tax_number: z.union([z.string(), z.null()]),
76
- tax_number_2: z.union([z.string(), z.null()]),
77
- company_number: z.union([z.string(), z.null()]),
78
- bank_account: z.union([
79
- z
80
- .object({
81
- type: z
82
- .enum(["iban", "us_domestic", "uk_domestic", "other"])
83
- .default("iban"),
84
- name: z.string(),
85
- bank_name: z.string(),
86
- iban: z.string(),
87
- account_number: z.string(),
88
- bic: z.string(),
89
- routing_number: z.string(),
90
- sort_code: z.string(),
91
- })
92
- .partial()
93
- .passthrough(),
94
- z.null(),
95
- ]),
96
- save_customer: z.boolean().default(true),
97
- })
80
+ .object({ save_customer: z.boolean().default(true) })
98
81
  .partial()
99
82
  .passthrough(),
100
83
  z.null(),
101
84
  ])
102
- .optional(),
85
+ ).optional(),
103
86
  note: z.union([z.string(), z.null()]).optional(),
104
87
  payment_terms: z.union([z.string(), z.null()]).optional(),
105
88
  tax_clause: z.union([z.string(), z.null()]).optional(),
@@ -117,19 +100,7 @@ const CompleteInvoicePreview = z.object({
117
100
  gross_price: z.number().optional(),
118
101
  quantity: z.number().gte(-140737488355328).lte(140737488355327),
119
102
  unit: z.union([z.string(), z.null()]).optional(),
120
- taxes: z
121
- .array(
122
- z
123
- .object({
124
- rate: z.number(),
125
- tax_id: z.string(),
126
- classification: z.string(),
127
- reverse_charge: z.boolean(),
128
- amount: z.number(),
129
- })
130
- .partial()
131
- )
132
- .optional(),
103
+ taxes: z.array(DocumentItemTax).optional(),
133
104
  discounts: z.array(LineDiscount).max(5).optional(),
134
105
  metadata: z
135
106
  .union([
@@ -189,84 +160,17 @@ const PartialInvoicePreview = z.object({
189
160
  .string()
190
161
  .regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/)
191
162
  .optional(),
192
- issuer: z
193
- .object({
194
- name: z.union([z.string(), z.null()]),
195
- email: z.union([z.string(), z.null()]),
196
- address: z.union([z.string(), z.null()]),
197
- address_2: z.union([z.string(), z.null()]),
198
- post_code: z.union([z.string(), z.null()]),
199
- city: z.union([z.string(), z.null()]),
200
- state: z.union([z.string(), z.null()]),
201
- country: z.union([z.string(), z.null()]),
202
- country_code: z.union([z.string(), z.null()]),
203
- tax_number: z.union([z.string(), z.null()]),
204
- tax_number_2: z.union([z.string(), z.null()]),
205
- company_number: z.union([z.string(), z.null()]),
206
- bank_account: z.union([
207
- z
208
- .object({
209
- type: z
210
- .enum(["iban", "us_domestic", "uk_domestic", "other"])
211
- .default("iban"),
212
- name: z.string(),
213
- bank_name: z.string(),
214
- iban: z.string(),
215
- account_number: z.string(),
216
- bic: z.string(),
217
- routing_number: z.string(),
218
- sort_code: z.string(),
219
- })
220
- .partial()
221
- .passthrough(),
222
- z.null(),
223
- ]),
224
- })
225
- .partial()
226
- .passthrough()
227
- .optional(),
163
+ issuer: DocumentEntity.optional(),
228
164
  customer_id: z.union([z.string(), z.null()]).optional(),
229
- customer: z
230
- .union([
165
+ customer: DocumentEntity.and(
166
+ z.union([
231
167
  z
232
- .object({
233
- name: z.union([z.string(), z.null()]),
234
- email: z.union([z.string(), z.null()]),
235
- address: z.union([z.string(), z.null()]),
236
- address_2: z.union([z.string(), z.null()]),
237
- post_code: z.union([z.string(), z.null()]),
238
- city: z.union([z.string(), z.null()]),
239
- state: z.union([z.string(), z.null()]),
240
- country: z.union([z.string(), z.null()]),
241
- country_code: z.union([z.string(), z.null()]),
242
- tax_number: z.union([z.string(), z.null()]),
243
- tax_number_2: z.union([z.string(), z.null()]),
244
- company_number: z.union([z.string(), z.null()]),
245
- bank_account: z.union([
246
- z
247
- .object({
248
- type: z
249
- .enum(["iban", "us_domestic", "uk_domestic", "other"])
250
- .default("iban"),
251
- name: z.string(),
252
- bank_name: z.string(),
253
- iban: z.string(),
254
- account_number: z.string(),
255
- bic: z.string(),
256
- routing_number: z.string(),
257
- sort_code: z.string(),
258
- })
259
- .partial()
260
- .passthrough(),
261
- z.null(),
262
- ]),
263
- save_customer: z.boolean().default(true),
264
- })
168
+ .object({ save_customer: z.boolean().default(true) })
265
169
  .partial()
266
170
  .passthrough(),
267
171
  z.null(),
268
172
  ])
269
- .optional(),
173
+ ).optional(),
270
174
  note: z.union([z.string(), z.null()]).optional(),
271
175
  payment_terms: z.union([z.string(), z.null()]).optional(),
272
176
  tax_clause: z.union([z.string(), z.null()]).optional(),
@@ -1,12 +1,12 @@
1
1
  import type {
2
- AdvanceInvoiceWithItems,
2
+ AdvanceInvoice,
3
3
  CreateAdvanceInvoiceRequest,
4
4
  CreateCreditNoteRequest,
5
5
  CreateEstimateRequest,
6
6
  CreateInvoiceRequest,
7
- CreditNoteWithItems,
8
- EstimateWithItems,
9
- InvoiceWithItems,
7
+ CreditNote,
8
+ Estimate,
9
+ Invoice,
10
10
  } from "@spaceinvoices/js-sdk";
11
11
  import { useQuery } from "@tanstack/react-query";
12
12
 
@@ -14,7 +14,7 @@ import { useEntities } from "@/ui/providers/entities-context";
14
14
  import { useSDK } from "@/ui/providers/sdk-provider";
15
15
 
16
16
  export type DocumentType = "invoice" | "estimate" | "credit_note" | "advance_invoice";
17
- type Document = InvoiceWithItems | EstimateWithItems | CreditNoteWithItems | AdvanceInvoiceWithItems;
17
+ type Document = Invoice | Estimate | CreditNote | AdvanceInvoice;
18
18
  type CreateRequest =
19
19
  | CreateInvoiceRequest
20
20
  | CreateEstimateRequest
@@ -56,16 +56,25 @@ export function getAllowedDuplicateTargets(sourceType: DocumentType): DocumentTy
56
56
  */
57
57
  function transformDocumentForDuplication(source: Document, _targetType: DocumentType): Partial<CreateRequest> {
58
58
  // Transform items - copy only the fields needed for creation
59
- const items = source.items?.map((item: (typeof source.items)[number]) => ({
59
+ // Use type assertion for items since all document item types share the same shape
60
+ const sourceItems = source.items as Array<{
61
+ name: string;
62
+ description: string | null;
63
+ quantity: number;
64
+ price: number;
65
+ gross_price?: number | null;
66
+ taxes: Array<{ tax_id?: string }>;
67
+ }>;
68
+ const items = sourceItems?.map((item) => ({
60
69
  name: item.name,
61
70
  description: item.description,
62
71
  quantity: item.quantity,
63
72
  // Use gross_price if set, otherwise use price. The form uses is_gross_price as a UI toggle.
64
- price: (item as { gross_price?: number }).gross_price ?? item.price,
73
+ price: item.gross_price ?? item.price,
65
74
  // Copy tax references (tax_id), not computed tax data
66
- taxes: item.taxes?.map((tax: { tax_id?: string }) => ({ tax_id: tax.tax_id })),
75
+ taxes: item.taxes?.map((tax) => ({ tax_id: tax.tax_id })),
67
76
  // Derive is_gross_price from whether gross_price is set
68
- gross_price: (item as { gross_price?: number }).gross_price,
77
+ gross_price: item.gross_price ?? undefined,
69
78
  }));
70
79
 
71
80
  // Build customer data - always copy if available (form needs this for display)
@@ -158,8 +167,7 @@ export function useDuplicateDocument({
158
167
  if (sourceType === "invoice") {
159
168
  source = await sdk.invoices.get(sourceId, undefined, { entity_id: activeEntity.id });
160
169
  } else if (sourceType === "estimate") {
161
- // estimates.get only takes 2 args (no params)
162
- source = await sdk.estimates.get(sourceId, { entity_id: activeEntity.id });
170
+ source = await sdk.estimates.get(sourceId, undefined, { entity_id: activeEntity.id });
163
171
  } else if (sourceType === "advance_invoice") {
164
172
  source = await sdk.advanceInvoices.get(sourceId, undefined, { entity_id: activeEntity.id });
165
173
  } else {
@@ -180,6 +180,27 @@ export function EntitiesProvider({
180
180
  }
181
181
  }, [memoizedEntities, urlEntityId]); // Re-run when URL entity changes
182
182
 
183
+ // When urlEntityId is provided but not found in current environment, auto-switch
184
+ const urlEntityFallbackAttempted = useRef<string | null>(null);
185
+ useEffect(() => {
186
+ if (!urlEntityId || isLoading) return;
187
+ if (memoizedEntities.length === 0) return;
188
+
189
+ const found = memoizedEntities.some((e) => e.id === urlEntityId);
190
+ if (found) {
191
+ urlEntityFallbackAttempted.current = null;
192
+ return;
193
+ }
194
+
195
+ // Prevent infinite switching between environments
196
+ if (urlEntityFallbackAttempted.current === urlEntityId) return;
197
+ urlEntityFallbackAttempted.current = urlEntityId;
198
+
199
+ // URL entity not in current environment — try the other
200
+ const altEnv: EntityEnvironment = environment === "live" ? "sandbox" : "live";
201
+ setEnvironmentState(altEnv);
202
+ }, [urlEntityId, memoizedEntities, isLoading, environment]);
203
+
183
204
  const cookieOpts = useMemo(
184
205
  () => ({
185
206
  path: "/",
@@ -1,196 +0,0 @@
1
- import type { Invoice } from "@spaceinvoices/js-sdk";
2
- import { AlertCircle, Check, CheckCircle2, Clock, Copy, ExternalLink, XCircle } from "lucide-react";
3
- import { useState } from "react";
4
- import { Alert, AlertDescription, AlertTitle } from "@/ui/components/ui/alert";
5
- import { Badge } from "@/ui/components/ui/badge";
6
- import { Button } from "@/ui/components/ui/button";
7
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/ui/components/ui/card";
8
- import type { ComponentTranslationProps } from "@/ui/lib/translation";
9
- import { createTranslation } from "@/ui/lib/translation";
10
- import de from "../../entities/fina-settings-form/locales/de";
11
- import en from "../../entities/fina-settings-form/locales/en";
12
- import sl from "../../entities/fina-settings-form/locales/sl";
13
-
14
- const translations = { de, sl, en } as const;
15
-
16
- type FinaData = {
17
- status?: "success" | "pending" | "failed";
18
- error?: string;
19
- fiscalized_at?: string;
20
- data?: {
21
- zki?: string;
22
- jir?: string;
23
- premise_id?: string;
24
- device_id?: string;
25
- invoice_number?: string;
26
- qr_code_url?: string;
27
- };
28
- };
29
-
30
- interface FinaInfoDisplayProps extends ComponentTranslationProps {
31
- invoice: Invoice;
32
- }
33
-
34
- export function FinaInfoDisplay({ invoice, t: translateFn, namespace, locale }: FinaInfoDisplayProps) {
35
- const [copiedField, setCopiedField] = useState<string | null>(null);
36
-
37
- const t = createTranslation({
38
- t: translateFn,
39
- namespace,
40
- locale,
41
- translations,
42
- });
43
-
44
- const fina = (invoice as any).fina as FinaData | undefined;
45
-
46
- if (!fina) {
47
- return null;
48
- }
49
-
50
- const copyToClipboard = async (text: string, fieldName: string) => {
51
- try {
52
- await navigator.clipboard.writeText(text);
53
- setCopiedField(fieldName);
54
- setTimeout(() => setCopiedField(null), 2000);
55
- } catch (err) {
56
- console.error("Failed to copy:", err);
57
- }
58
- };
59
-
60
- const getStatusBadge = () => {
61
- switch (fina.status) {
62
- case "success":
63
- return (
64
- <Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100">
65
- <CheckCircle2 className="mr-1 h-3 w-3" />
66
- {t("Fiscalized")}
67
- </Badge>
68
- );
69
- case "pending":
70
- return (
71
- <Badge className="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100">
72
- <Clock className="mr-1 h-3 w-3" />
73
- {t("Pending")}
74
- </Badge>
75
- );
76
- case "failed":
77
- return (
78
- <Badge className="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100">
79
- <XCircle className="mr-1 h-3 w-3" />
80
- {t("Failed")}
81
- </Badge>
82
- );
83
- default:
84
- return null;
85
- }
86
- };
87
-
88
- return (
89
- <Card>
90
- <CardHeader>
91
- <CardTitle className="flex items-center gap-2">
92
- {t("FINA Fiscalization")}
93
- {getStatusBadge()}
94
- </CardTitle>
95
- <CardDescription>{t("Croatian tax authority fiscalization details")}</CardDescription>
96
- </CardHeader>
97
- <CardContent className="space-y-4">
98
- {/* Error Message */}
99
- {fina.status === "failed" && fina.error && (
100
- <Alert variant="destructive">
101
- <AlertCircle className="h-4 w-4" />
102
- <AlertTitle>{t("Fiscalization Error")}</AlertTitle>
103
- <AlertDescription>{fina.error}</AlertDescription>
104
- </Alert>
105
- )}
106
-
107
- {/* FINA Data */}
108
- {fina.data && (
109
- <div className="space-y-3">
110
- {/* ZKI Code */}
111
- {fina.data.zki && (
112
- <div className="flex items-center justify-between rounded-lg border p-3">
113
- <div className="space-y-1">
114
- <p className="font-medium text-sm">{t("ZKI")}</p>
115
- <p className="font-mono text-muted-foreground text-xs">{fina.data.zki}</p>
116
- </div>
117
- <Button variant="ghost" size="sm" onClick={() => copyToClipboard(fina.data!.zki!, "zki")}>
118
- {copiedField === "zki" ? <Check className="h-4 w-4 text-green-600" /> : <Copy className="h-4 w-4" />}
119
- </Button>
120
- </div>
121
- )}
122
-
123
- {/* JIR Code */}
124
- {fina.data.jir && (
125
- <div className="flex items-center justify-between rounded-lg border p-3">
126
- <div className="space-y-1">
127
- <p className="font-medium text-sm">{t("JIR")}</p>
128
- <p className="font-mono text-muted-foreground text-xs">{fina.data.jir}</p>
129
- </div>
130
- <Button variant="ghost" size="sm" onClick={() => copyToClipboard(fina.data!.jir!, "jir")}>
131
- {copiedField === "jir" ? <Check className="h-4 w-4 text-green-600" /> : <Copy className="h-4 w-4" />}
132
- </Button>
133
- </div>
134
- )}
135
-
136
- {/* Business Premise & Device */}
137
- <div className="grid grid-cols-2 gap-3">
138
- {fina.data.premise_id && (
139
- <div className="space-y-1">
140
- <p className="font-medium text-sm">{t("Business Premise")}</p>
141
- <p className="text-muted-foreground text-sm">{fina.data.premise_id}</p>
142
- </div>
143
- )}
144
- {fina.data.device_id && (
145
- <div className="space-y-1">
146
- <p className="font-medium text-sm">{t("Electronic Device")}</p>
147
- <p className="text-muted-foreground text-sm">{fina.data.device_id}</p>
148
- </div>
149
- )}
150
- </div>
151
-
152
- {/* Invoice Number */}
153
- {fina.data.invoice_number && (
154
- <div className="space-y-1">
155
- <p className="font-medium text-sm">{t("Invoice Number")}</p>
156
- <p className="text-muted-foreground text-sm">{fina.data.invoice_number}</p>
157
- </div>
158
- )}
159
-
160
- {/* QR Code URL */}
161
- {fina.data.qr_code_url && (
162
- <div className="flex items-center justify-between rounded-lg border p-3">
163
- <div className="space-y-1">
164
- <p className="font-medium text-sm">{t("QR Code")}</p>
165
- <a
166
- href={fina.data.qr_code_url}
167
- target="_blank"
168
- rel="noopener noreferrer"
169
- className="flex items-center gap-1 text-primary text-xs hover:underline"
170
- >
171
- {t("QR Code")}
172
- <ExternalLink className="h-3 w-3" />
173
- </a>
174
- </div>
175
- <Button variant="ghost" size="sm" onClick={() => copyToClipboard(fina.data!.qr_code_url!, "qr_url")}>
176
- {copiedField === "qr_url" ? (
177
- <Check className="h-4 w-4 text-green-600" />
178
- ) : (
179
- <Copy className="h-4 w-4" />
180
- )}
181
- </Button>
182
- </div>
183
- )}
184
- </div>
185
- )}
186
-
187
- {/* Fiscalized Timestamp */}
188
- {fina.fiscalized_at && (
189
- <div className="pt-2 text-muted-foreground text-sm">
190
- {t("Fiscalized at")}: {new Date(fina.fiscalized_at).toLocaleString()}
191
- </div>
192
- )}
193
- </CardContent>
194
- </Card>
195
- );
196
- }