@spaceinvoices/react-ui 0.4.4 → 0.4.6

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 (178) hide show
  1. package/cli/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/components/advance-invoices/advance-invoices.hooks.ts +2 -2
  4. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +151 -45
  5. package/src/components/advance-invoices/create/locales/de.ts +20 -0
  6. package/src/components/advance-invoices/create/locales/es.ts +20 -0
  7. package/src/components/advance-invoices/create/locales/fr.ts +20 -0
  8. package/src/components/advance-invoices/create/locales/hr.ts +20 -0
  9. package/src/components/advance-invoices/create/locales/it.ts +20 -0
  10. package/src/components/advance-invoices/create/locales/nl.ts +20 -0
  11. package/src/components/advance-invoices/create/locales/pl.ts +20 -0
  12. package/src/components/advance-invoices/create/locales/pt.ts +20 -0
  13. package/src/components/advance-invoices/create/locales/sl.ts +20 -0
  14. package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +5 -5
  15. package/src/components/advance-invoices/list/list-row-actions.tsx +48 -1
  16. package/src/components/advance-invoices/list/list-table.tsx +21 -1
  17. package/src/components/advance-invoices/list/locales/de.ts +4 -0
  18. package/src/components/advance-invoices/list/locales/en.ts +3 -0
  19. package/src/components/advance-invoices/list/locales/es.ts +4 -0
  20. package/src/components/advance-invoices/list/locales/fr.ts +4 -0
  21. package/src/components/advance-invoices/list/locales/hr.ts +3 -0
  22. package/src/components/advance-invoices/list/locales/it.ts +4 -0
  23. package/src/components/advance-invoices/list/locales/nl.ts +4 -0
  24. package/src/components/advance-invoices/list/locales/pl.ts +3 -0
  25. package/src/components/advance-invoices/list/locales/pt.ts +4 -0
  26. package/src/components/advance-invoices/list/locales/sl.ts +3 -0
  27. package/src/components/credit-notes/create/create-credit-note-form.tsx +161 -42
  28. package/src/components/credit-notes/create/locales/de.ts +21 -0
  29. package/src/components/credit-notes/create/locales/es.ts +21 -0
  30. package/src/components/credit-notes/create/locales/fr.ts +21 -0
  31. package/src/components/credit-notes/create/locales/hr.ts +21 -0
  32. package/src/components/credit-notes/create/locales/it.ts +21 -0
  33. package/src/components/credit-notes/create/locales/nl.ts +21 -0
  34. package/src/components/credit-notes/create/locales/pl.ts +21 -0
  35. package/src/components/credit-notes/create/locales/pt.ts +21 -0
  36. package/src/components/credit-notes/create/locales/sl.ts +22 -1
  37. package/src/components/credit-notes/credit-notes.hooks.ts +2 -2
  38. package/src/components/credit-notes/list/list-row-actions.tsx +44 -1
  39. package/src/components/credit-notes/list/list-table.tsx +16 -2
  40. package/src/components/credit-notes/list/locales/de.ts +2 -0
  41. package/src/components/credit-notes/list/locales/en.ts +2 -0
  42. package/src/components/credit-notes/list/locales/es.ts +2 -0
  43. package/src/components/credit-notes/list/locales/fr.ts +2 -0
  44. package/src/components/credit-notes/list/locales/hr.ts +2 -0
  45. package/src/components/credit-notes/list/locales/it.ts +2 -0
  46. package/src/components/credit-notes/list/locales/nl.ts +2 -0
  47. package/src/components/credit-notes/list/locales/pl.ts +2 -0
  48. package/src/components/credit-notes/list/locales/pt.ts +2 -0
  49. package/src/components/credit-notes/list/locales/sl.ts +2 -0
  50. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -9
  51. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +77 -48
  52. package/src/components/dashboard/shared/use-revenue-data.ts +77 -9
  53. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +114 -7
  54. package/src/components/delivery-notes/create/locales/de.ts +21 -0
  55. package/src/components/delivery-notes/create/locales/es.ts +21 -0
  56. package/src/components/delivery-notes/create/locales/fr.ts +21 -0
  57. package/src/components/delivery-notes/create/locales/hr.ts +21 -0
  58. package/src/components/delivery-notes/create/locales/it.ts +21 -0
  59. package/src/components/delivery-notes/create/locales/nl.ts +21 -0
  60. package/src/components/delivery-notes/create/locales/pl.ts +21 -0
  61. package/src/components/delivery-notes/create/locales/pt.ts +21 -0
  62. package/src/components/delivery-notes/create/locales/sl.ts +22 -1
  63. package/src/components/delivery-notes/list/list-table.tsx +17 -8
  64. package/src/components/delivery-notes/list/locales/de.ts +32 -1
  65. package/src/components/delivery-notes/list/locales/en.ts +31 -0
  66. package/src/components/delivery-notes/list/locales/es.ts +32 -1
  67. package/src/components/delivery-notes/list/locales/fr.ts +32 -1
  68. package/src/components/delivery-notes/list/locales/hr.ts +32 -1
  69. package/src/components/delivery-notes/list/locales/it.ts +32 -1
  70. package/src/components/delivery-notes/list/locales/nl.ts +32 -1
  71. package/src/components/delivery-notes/list/locales/pl.ts +32 -1
  72. package/src/components/delivery-notes/list/locales/pt.ts +32 -1
  73. package/src/components/delivery-notes/list/locales/sl.ts +32 -1
  74. package/src/components/documents/create/document-add-item-form.tsx +70 -0
  75. package/src/components/documents/create/document-details-section.tsx +590 -344
  76. package/src/components/documents/create/document-items-section.tsx +21 -4
  77. package/src/components/documents/create/live-preview.tsx +24 -4
  78. package/src/components/documents/create/mark-as-paid-section.tsx +29 -20
  79. package/src/components/documents/create/prepare-document-submission.ts +22 -8
  80. package/src/components/documents/create/smart-code-insert-button.tsx +6 -0
  81. package/src/components/documents/shared/document-preview-display.tsx +3 -4
  82. package/src/components/documents/view/document-actions-bar.tsx +7 -27
  83. package/src/components/documents/view/document-details-card.tsx +32 -9
  84. package/src/components/documents/view/locales/de.ts +3 -0
  85. package/src/components/documents/view/locales/es.ts +3 -0
  86. package/src/components/documents/view/locales/fr.ts +3 -0
  87. package/src/components/documents/view/locales/hr.ts +3 -0
  88. package/src/components/documents/view/locales/it.ts +3 -0
  89. package/src/components/documents/view/locales/nl.ts +3 -0
  90. package/src/components/documents/view/locales/pl.ts +3 -0
  91. package/src/components/documents/view/locales/pt.ts +3 -0
  92. package/src/components/documents/view/locales/sl.ts +4 -1
  93. package/src/components/documents/view/use-document-download.ts +6 -3
  94. package/src/components/entities/create-entity-form.tsx +2 -1
  95. package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +6 -0
  96. package/src/components/entities/entity-settings-form/input-with-preview.tsx +2 -117
  97. package/src/components/entities/entity-settings-form/locales/de.ts +5 -0
  98. package/src/components/entities/entity-settings-form/locales/es.ts +5 -0
  99. package/src/components/entities/entity-settings-form/locales/fr.ts +5 -0
  100. package/src/components/entities/entity-settings-form/locales/hr.ts +5 -0
  101. package/src/components/entities/entity-settings-form/locales/it.ts +5 -0
  102. package/src/components/entities/entity-settings-form/locales/nl.ts +5 -0
  103. package/src/components/entities/entity-settings-form/locales/pl.ts +5 -0
  104. package/src/components/entities/entity-settings-form/locales/pt.ts +5 -0
  105. package/src/components/entities/entity-settings-form/locales/sl.ts +5 -0
  106. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +15 -0
  107. package/src/components/entities/fina-settings-form/fina-settings.hooks.ts +5 -1
  108. package/src/components/entities/fina-settings-form/locales/de.ts +3 -0
  109. package/src/components/entities/fina-settings-form/locales/en.ts +3 -0
  110. package/src/components/entities/fina-settings-form/locales/es.ts +3 -0
  111. package/src/components/entities/fina-settings-form/locales/fr.ts +3 -0
  112. package/src/components/entities/fina-settings-form/locales/hr.ts +3 -0
  113. package/src/components/entities/fina-settings-form/locales/it.ts +3 -0
  114. package/src/components/entities/fina-settings-form/locales/nl.ts +3 -0
  115. package/src/components/entities/fina-settings-form/locales/pl.ts +3 -0
  116. package/src/components/entities/fina-settings-form/locales/pt.ts +3 -0
  117. package/src/components/entities/fina-settings-form/locales/sl.ts +3 -0
  118. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +4 -4
  119. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +3 -3
  120. package/src/components/entities/settings/company-settings-form.tsx +173 -20
  121. package/src/components/entities/settings/defaults-settings-form.tsx +38 -1
  122. package/src/components/entities/settings/tax-rules-settings-form.tsx +1 -2
  123. package/src/components/estimates/create/create-estimate-form.tsx +107 -8
  124. package/src/components/estimates/create/locales/de.ts +21 -0
  125. package/src/components/estimates/create/locales/es.ts +21 -0
  126. package/src/components/estimates/create/locales/fr.ts +21 -0
  127. package/src/components/estimates/create/locales/hr.ts +21 -0
  128. package/src/components/estimates/create/locales/it.ts +21 -0
  129. package/src/components/estimates/create/locales/nl.ts +21 -0
  130. package/src/components/estimates/create/locales/pl.ts +21 -0
  131. package/src/components/estimates/create/locales/pt.ts +21 -0
  132. package/src/components/estimates/create/locales/sl.ts +22 -1
  133. package/src/components/estimates/list/list-table.tsx +11 -2
  134. package/src/components/estimates/list/locales/de.ts +1 -0
  135. package/src/components/estimates/list/locales/en.ts +1 -0
  136. package/src/components/estimates/list/locales/es.ts +1 -0
  137. package/src/components/estimates/list/locales/fr.ts +1 -0
  138. package/src/components/estimates/list/locales/hr.ts +1 -0
  139. package/src/components/estimates/list/locales/it.ts +1 -0
  140. package/src/components/estimates/list/locales/nl.ts +1 -0
  141. package/src/components/estimates/list/locales/pl.ts +1 -0
  142. package/src/components/estimates/list/locales/pt.ts +1 -0
  143. package/src/components/estimates/list/locales/sl.ts +1 -0
  144. package/src/components/export/document-export-form.tsx +46 -12
  145. package/src/components/invoices/create/create-invoice-form.tsx +186 -48
  146. package/src/components/invoices/create/locales/de.ts +28 -0
  147. package/src/components/invoices/create/locales/es.ts +28 -0
  148. package/src/components/invoices/create/locales/fr.ts +28 -0
  149. package/src/components/invoices/create/locales/hr.ts +28 -0
  150. package/src/components/invoices/create/locales/it.ts +28 -0
  151. package/src/components/invoices/create/locales/nl.ts +28 -0
  152. package/src/components/invoices/create/locales/pl.ts +28 -0
  153. package/src/components/invoices/create/locales/pt.ts +28 -0
  154. package/src/components/invoices/create/locales/sl.ts +29 -1
  155. package/src/components/invoices/create/prepare-invoice-submission.ts +5 -5
  156. package/src/components/invoices/invoices.hooks.ts +2 -2
  157. package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
  158. package/src/components/table/search-input.tsx +17 -0
  159. package/src/components/table/table-pagination.tsx +1 -1
  160. package/src/generated/schemas/advanceinvoice.ts +6 -2
  161. package/src/generated/schemas/creditnote.ts +3 -1
  162. package/src/generated/schemas/deliverynote.ts +3 -1
  163. package/src/generated/schemas/entity.ts +4 -4
  164. package/src/generated/schemas/entityapikey.ts +19 -0
  165. package/src/generated/schemas/estimate.ts +6 -2
  166. package/src/generated/schemas/index.ts +1 -0
  167. package/src/generated/schemas/invoice.ts +4 -1
  168. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +17 -23
  169. package/src/generated/schemas/rendercreditnotepreview_body.ts +17 -23
  170. package/src/generated/schemas/renderdeliverynotepreview_body.ts +17 -23
  171. package/src/generated/schemas/renderestimatepreview_body.ts +17 -23
  172. package/src/generated/schemas/renderinvoicepreview_body.ts +19 -23
  173. package/src/generated/schemas/startpdfexport_body.ts +14 -2
  174. package/src/generated/schemas/webhook.ts +4 -0
  175. package/src/hooks/use-duplicate-document.ts +16 -9
  176. package/src/hooks/use-vies-check.ts +3 -0
  177. package/src/lib/template-variables.tsx +167 -0
  178. package/src/providers/entities-context.tsx +2 -2
@@ -2,11 +2,13 @@
2
2
  * Shared document details section for invoices and estimates
3
3
  * Handles: number, date, and document-type-specific date field (date_due or date_valid_till)
4
4
  */
5
- import type { Entity, Estimate, Invoice } from "@spaceinvoices/js-sdk";
6
- import { CalendarIcon } from "lucide-react";
5
+ import type { Entity, Estimate, Invoice, ViesCheckResponse } from "@spaceinvoices/js-sdk";
6
+ import { CalendarIcon, ChevronDown, Globe, Info, Loader2 } from "lucide-react";
7
7
  import { useRef, useState } from "react";
8
+ import { Badge } from "@/ui/components/ui/badge";
8
9
  import { Button } from "@/ui/components/ui/button";
9
10
  import { Calendar } from "@/ui/components/ui/calendar";
11
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/ui/components/ui/collapsible";
10
12
  import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/ui/components/ui/form";
11
13
  import { Input } from "@/ui/components/ui/input";
12
14
  import { Popover, PopoverContent, PopoverTrigger } from "@/ui/components/ui/popover";
@@ -14,6 +16,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
14
16
  import { Textarea } from "@/ui/components/ui/textarea";
15
17
  import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
16
18
  import { CURRENCY_CODES } from "@/ui/lib/constants";
19
+ import { replaceTemplateVariablesForPreview } from "@/ui/lib/template-variables";
17
20
  import { cn } from "@/ui/lib/utils";
18
21
  import type { DocumentTypes } from "../types";
19
22
  import type { AnyControl } from "./form-types";
@@ -41,12 +44,12 @@ type FursInlineProps = {
41
44
 
42
45
  type FinaPremise = {
43
46
  id: string;
44
- premise_id: string;
47
+ business_premise_name: string;
45
48
  };
46
49
 
47
50
  type FinaDevice = {
48
51
  id: string;
49
- device_id: string;
52
+ electronic_device_name: string;
50
53
  };
51
54
 
52
55
  type FinaInlineProps = {
@@ -63,6 +66,22 @@ type ServiceDateProps = {
63
66
  onDateTypeChange: (type: "single" | "range") => void;
64
67
  };
65
68
 
69
+ const DUE_DAYS_PRESETS = [0, 7, 14, 30, 60, 90] as const;
70
+
71
+ type DueDaysProps = {
72
+ dueDaysType: number | "custom";
73
+ onDueDaysTypeChange: (type: number | "custom") => void;
74
+ };
75
+
76
+ const LABEL_WIDTH = "w-[6.5rem] shrink-0";
77
+
78
+ function extractSequenceNumber(fullNumber: string, premise?: string, device?: string): string {
79
+ if (!fullNumber || (!premise && !device)) return fullNumber;
80
+ const parts = fullNumber.split(/[-/]/);
81
+ const filtered = parts.filter((part) => !(premise && part === premise) && !(device && part === device));
82
+ return filtered.join("") || fullNumber;
83
+ }
84
+
66
85
  type DocumentDetailsSectionProps = {
67
86
  control: AnyControl;
68
87
  documentType: DocumentTypes;
@@ -71,6 +90,7 @@ type DocumentDetailsSectionProps = {
71
90
  fursInline?: FursInlineProps; // FURS premise/device inline with number
72
91
  finaInline?: FinaInlineProps; // FINA premise/device inline with number
73
92
  serviceDate?: ServiceDateProps; // Service date section (invoice only)
93
+ dueDays?: DueDaysProps; // Due days selector (invoice only)
74
94
  };
75
95
 
76
96
  export function DocumentDetailsSection({
@@ -81,6 +101,7 @@ export function DocumentDetailsSection({
81
101
  fursInline,
82
102
  finaInline,
83
103
  serviceDate,
104
+ dueDays,
84
105
  }: DocumentDetailsSectionProps) {
85
106
  // Determine the date field name based on document type
86
107
  // Delivery notes don't have a secondary date field
@@ -93,93 +114,124 @@ export function DocumentDetailsSection({
93
114
  const showFinaSelects = !!finaInline;
94
115
 
95
116
  return (
96
- <div className="flex-1 space-y-4">
117
+ <div className="flex-1 space-y-3">
97
118
  <h2 className="font-bold text-xl">{t("Details")}</h2>
98
119
 
99
- {/* Number field - with optional FURS premise/device inline (Premise | Device | Number) */}
120
+ {/* Number field - inline with optional FURS/FINA premise/device + sequence number */}
100
121
  <FormField
101
122
  control={control}
102
123
  name="number"
103
124
  render={({ field }) => (
104
125
  <FormItem>
105
- <FormLabel>{t("Number")} *</FormLabel>
106
- {showFursSelects ? (
107
- <div className="flex gap-2">
108
- <Select
109
- value={fursInline.selectedPremise || ""}
110
- onValueChange={(v) => fursInline.onPremiseChange(v ?? undefined)}
111
- >
112
- <SelectTrigger className="w-24">
113
- <SelectValue placeholder={t("Premise")} />
114
- </SelectTrigger>
115
- <SelectContent>
116
- {fursInline.premises.map((premise) => (
117
- <SelectItem key={premise.id} value={premise.business_premise_name}>
118
- {premise.business_premise_name}
119
- </SelectItem>
120
- ))}
121
- </SelectContent>
122
- </Select>
123
- <Select
124
- value={fursInline.selectedDevice || ""}
125
- onValueChange={(v) => fursInline.onDeviceChange(v ?? undefined)}
126
- disabled={!fursInline.selectedPremise || fursInline.devices.length === 0}
127
- >
128
- <SelectTrigger className="w-24">
129
- <SelectValue placeholder={t("Device")} />
130
- </SelectTrigger>
131
- <SelectContent>
132
- {fursInline.devices.map((device) => (
133
- <SelectItem key={device.id} value={device.electronic_device_name}>
134
- {device.electronic_device_name}
135
- </SelectItem>
136
- ))}
137
- </SelectContent>
138
- </Select>
139
- <Tooltip>
140
- <TooltipTrigger asChild>
141
- <FormControl>
142
- <Input {...field} disabled className="flex-1" />
143
- </FormControl>
144
- </TooltipTrigger>
145
- <TooltipContent>
146
- <p>{t("Number format can be changed in settings")}</p>
147
- </TooltipContent>
148
- </Tooltip>
149
- </div>
150
- ) : showFinaSelects ? (
151
- <div className="flex gap-2">
152
- <Select
153
- value={finaInline.selectedPremise || ""}
154
- onValueChange={(v) => finaInline.onPremiseChange(v ?? undefined)}
155
- >
156
- <SelectTrigger className="w-24">
157
- <SelectValue placeholder={t("Premise")} />
158
- </SelectTrigger>
159
- <SelectContent>
160
- {finaInline.premises.map((premise) => (
161
- <SelectItem key={premise.id} value={premise.premise_id}>
162
- {premise.premise_id}
163
- </SelectItem>
164
- ))}
165
- </SelectContent>
166
- </Select>
167
- <Select
168
- value={finaInline.selectedDevice || ""}
169
- onValueChange={(v) => finaInline.onDeviceChange(v ?? undefined)}
170
- disabled={!finaInline.selectedPremise || finaInline.devices.length === 0}
171
- >
172
- <SelectTrigger className="w-24">
173
- <SelectValue placeholder={t("Device")} />
174
- </SelectTrigger>
175
- <SelectContent>
176
- {finaInline.devices.map((device) => (
177
- <SelectItem key={device.id} value={device.device_id}>
178
- {device.device_id}
179
- </SelectItem>
180
- ))}
181
- </SelectContent>
182
- </Select>
126
+ <div className="flex items-center gap-3">
127
+ <FormLabel className={LABEL_WIDTH}>{t("Number")} *</FormLabel>
128
+ {showFursSelects ? (
129
+ <div className="flex flex-1 items-center gap-2">
130
+ <Select
131
+ value={fursInline.selectedPremise || ""}
132
+ onValueChange={(v) => fursInline.onPremiseChange(v ?? undefined)}
133
+ >
134
+ <SelectTrigger className="w-24">
135
+ <SelectValue placeholder={t("Premise")} />
136
+ </SelectTrigger>
137
+ <SelectContent>
138
+ {fursInline.premises.map((premise) => (
139
+ <SelectItem key={premise.id} value={premise.business_premise_name}>
140
+ {premise.business_premise_name}
141
+ </SelectItem>
142
+ ))}
143
+ </SelectContent>
144
+ </Select>
145
+ <Select
146
+ value={fursInline.selectedDevice || ""}
147
+ onValueChange={(v) => fursInline.onDeviceChange(v ?? undefined)}
148
+ disabled={!fursInline.selectedPremise || fursInline.devices.length === 0}
149
+ >
150
+ <SelectTrigger className="w-24">
151
+ <SelectValue placeholder={t("Device")} />
152
+ </SelectTrigger>
153
+ <SelectContent>
154
+ {fursInline.devices.map((device) => (
155
+ <SelectItem key={device.id} value={device.electronic_device_name}>
156
+ {device.electronic_device_name}
157
+ </SelectItem>
158
+ ))}
159
+ </SelectContent>
160
+ </Select>
161
+ <Tooltip>
162
+ <TooltipTrigger asChild>
163
+ <FormControl>
164
+ <Input
165
+ disabled
166
+ readOnly
167
+ className="flex-1 text-right"
168
+ value={extractSequenceNumber(
169
+ field.value || "",
170
+ fursInline.selectedPremise,
171
+ fursInline.selectedDevice,
172
+ )}
173
+ />
174
+ </FormControl>
175
+ </TooltipTrigger>
176
+ <TooltipContent>
177
+ <p>{field.value || t("Number format can be changed in settings")}</p>
178
+ </TooltipContent>
179
+ </Tooltip>
180
+ </div>
181
+ ) : showFinaSelects ? (
182
+ <div className="flex flex-1 items-center gap-2">
183
+ <Select
184
+ value={finaInline.selectedPremise || ""}
185
+ onValueChange={(v) => finaInline.onPremiseChange(v ?? undefined)}
186
+ >
187
+ <SelectTrigger className="w-24">
188
+ <SelectValue placeholder={t("Premise")} />
189
+ </SelectTrigger>
190
+ <SelectContent>
191
+ {finaInline.premises.map((premise) => (
192
+ <SelectItem key={premise.id} value={premise.business_premise_name}>
193
+ {premise.business_premise_name}
194
+ </SelectItem>
195
+ ))}
196
+ </SelectContent>
197
+ </Select>
198
+ <Select
199
+ value={finaInline.selectedDevice || ""}
200
+ onValueChange={(v) => finaInline.onDeviceChange(v ?? undefined)}
201
+ disabled={!finaInline.selectedPremise || finaInline.devices.length === 0}
202
+ >
203
+ <SelectTrigger className="w-24">
204
+ <SelectValue placeholder={t("Device")} />
205
+ </SelectTrigger>
206
+ <SelectContent>
207
+ {finaInline.devices.map((device) => (
208
+ <SelectItem key={device.id} value={device.electronic_device_name}>
209
+ {device.electronic_device_name}
210
+ </SelectItem>
211
+ ))}
212
+ </SelectContent>
213
+ </Select>
214
+ <Tooltip>
215
+ <TooltipTrigger asChild>
216
+ <FormControl>
217
+ <Input
218
+ disabled
219
+ readOnly
220
+ className="flex-1 text-right"
221
+ value={extractSequenceNumber(
222
+ field.value || "",
223
+ finaInline.selectedPremise,
224
+ finaInline.selectedDevice,
225
+ )}
226
+ />
227
+ </FormControl>
228
+ </TooltipTrigger>
229
+ <TooltipContent>
230
+ <p>{field.value || t("Number format can be changed in settings")}</p>
231
+ </TooltipContent>
232
+ </Tooltip>
233
+ </div>
234
+ ) : (
183
235
  <Tooltip>
184
236
  <TooltipTrigger asChild>
185
237
  <FormControl>
@@ -190,19 +242,9 @@ export function DocumentDetailsSection({
190
242
  <p>{t("Number format can be changed in settings")}</p>
191
243
  </TooltipContent>
192
244
  </Tooltip>
193
- </div>
194
- ) : (
195
- <Tooltip>
196
- <TooltipTrigger asChild>
197
- <FormControl>
198
- <Input {...field} disabled />
199
- </FormControl>
200
- </TooltipTrigger>
201
- <TooltipContent>
202
- <p>{t("Number format can be changed in settings")}</p>
203
- </TooltipContent>
204
- </Tooltip>
205
- )}
245
+ )}
246
+ </div>
247
+
206
248
  <FormMessage />
207
249
  </FormItem>
208
250
  )}
@@ -213,64 +255,70 @@ export function DocumentDetailsSection({
213
255
  name="date"
214
256
  render={({ field }) => (
215
257
  <FormItem>
216
- <FormLabel className="">{t("Date")} *</FormLabel>
217
- {showFinaSelects ? (
218
- <Tooltip>
219
- <TooltipTrigger asChild>
220
- <FormControl>
221
- <Button variant="outline" disabled className="w-full pl-3 text-left font-normal">
222
- {new Date().toLocaleDateString()}
223
- <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
224
- </Button>
225
- </FormControl>
226
- </TooltipTrigger>
227
- <TooltipContent>
228
- <p>{t("FINA fiscalized invoices always use the current date")}</p>
229
- </TooltipContent>
230
- </Tooltip>
231
- ) : (
232
- <Popover>
233
- <PopoverTrigger asChild>
234
- <FormControl>
235
- <Button
236
- variant="outline"
237
- className={cn("w-full pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
238
- >
239
- {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
240
- <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
241
- </Button>
242
- </FormControl>
243
- </PopoverTrigger>
244
- <PopoverContent className="w-auto p-0" align="start">
245
- <Calendar
246
- mode="single"
247
- selected={field.value ? new Date(field.value) : undefined}
248
- onSelect={(date) => field.onChange(date?.toISOString())}
249
- disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
250
- initialFocus
251
- />
252
- </PopoverContent>
253
- </Popover>
254
- )}
258
+ <div className="flex items-center gap-3">
259
+ <FormLabel className={LABEL_WIDTH}>{t("Date")} *</FormLabel>
260
+ {showFinaSelects ? (
261
+ <Tooltip>
262
+ <TooltipTrigger asChild>
263
+ <FormControl>
264
+ <Button variant="outline" disabled className="flex-1 pl-3 text-left font-normal">
265
+ {new Date().toLocaleDateString()}
266
+ <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
267
+ </Button>
268
+ </FormControl>
269
+ </TooltipTrigger>
270
+ <TooltipContent>
271
+ <p>{t("FINA fiscalized invoices always use the current date")}</p>
272
+ </TooltipContent>
273
+ </Tooltip>
274
+ ) : (
275
+ <Popover>
276
+ <PopoverTrigger asChild>
277
+ <FormControl>
278
+ <Button
279
+ variant="outline"
280
+ className={cn("flex-1 pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
281
+ >
282
+ {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
283
+ <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
284
+ </Button>
285
+ </FormControl>
286
+ </PopoverTrigger>
287
+ <PopoverContent className="w-auto p-0" align="start">
288
+ <Calendar
289
+ mode="single"
290
+ selected={field.value ? new Date(field.value) : undefined}
291
+ onSelect={(date) => field.onChange(date?.toISOString())}
292
+ disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
293
+ initialFocus
294
+ />
295
+ </PopoverContent>
296
+ </Popover>
297
+ )}
298
+ </div>
255
299
  <FormMessage />
256
300
  </FormItem>
257
301
  )}
258
302
  />
259
303
 
260
- {/* Service Date - Invoice only */}
304
+ {/* Service Date - select replaces label */}
261
305
  {serviceDate && (
262
306
  <FormField
263
307
  control={control}
264
308
  name="date_service"
265
309
  render={({ field }) => (
266
310
  <FormItem>
267
- <div className="flex items-center justify-between">
268
- <FormLabel>{t("Service Date")}</FormLabel>
311
+ <div className="flex items-center gap-3">
269
312
  <Select
270
313
  value={serviceDate.dateType}
271
314
  onValueChange={(v) => serviceDate.onDateTypeChange(v as "single" | "range")}
272
315
  >
273
- <SelectTrigger className="h-7 w-auto gap-1 border-none px-2 font-normal text-xs shadow-none">
316
+ <SelectTrigger
317
+ className={cn(
318
+ LABEL_WIDTH,
319
+ "h-auto border-none p-0 font-medium text-sm shadow-none [&>svg]:ml-1 [&>svg]:size-3.5",
320
+ )}
321
+ >
274
322
  <SelectValue>{serviceDate.dateType === "single" ? t("Single Date") : t("Date Range")}</SelectValue>
275
323
  </SelectTrigger>
276
324
  <SelectContent>
@@ -278,40 +326,15 @@ export function DocumentDetailsSection({
278
326
  <SelectItem value="range">{t("Date Range")}</SelectItem>
279
327
  </SelectContent>
280
328
  </Select>
281
- </div>
282
-
283
- {serviceDate.dateType === "single" ? (
284
- <Popover>
285
- <PopoverTrigger asChild>
286
- <FormControl>
287
- <Button
288
- variant="outline"
289
- className={cn("w-full pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
290
- >
291
- {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
292
- <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
293
- </Button>
294
- </FormControl>
295
- </PopoverTrigger>
296
- <PopoverContent className="w-auto p-0" align="start">
297
- <Calendar
298
- mode="single"
299
- selected={field.value ? new Date(field.value) : undefined}
300
- onSelect={(date) => field.onChange(date?.toISOString())}
301
- initialFocus
302
- />
303
- </PopoverContent>
304
- </Popover>
305
- ) : (
306
- <div className="grid grid-cols-2 gap-2">
329
+ {serviceDate.dateType === "single" ? (
307
330
  <Popover>
308
331
  <PopoverTrigger asChild>
309
332
  <FormControl>
310
333
  <Button
311
334
  variant="outline"
312
- className={cn("w-full pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
335
+ className={cn("flex-1 pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
313
336
  >
314
- {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("From")}</span>}
337
+ {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
315
338
  <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
316
339
  </Button>
317
340
  </FormControl>
@@ -325,37 +348,61 @@ export function DocumentDetailsSection({
325
348
  />
326
349
  </PopoverContent>
327
350
  </Popover>
328
-
329
- <FormField
330
- control={control}
331
- name="date_service_to"
332
- render={({ field: toField }) => (
333
- <Popover>
334
- <PopoverTrigger asChild>
351
+ ) : (
352
+ <div className="grid flex-1 grid-cols-2 gap-2">
353
+ <Popover>
354
+ <PopoverTrigger asChild>
355
+ <FormControl>
335
356
  <Button
336
357
  variant="outline"
337
- className={cn(
338
- "w-full pl-3 text-left font-normal",
339
- !toField.value && "text-muted-foreground",
340
- )}
358
+ className={cn("w-full pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
341
359
  >
342
- {toField.value ? new Date(toField.value).toLocaleDateString() : <span>{t("To")}</span>}
360
+ {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("From")}</span>}
343
361
  <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
344
362
  </Button>
345
- </PopoverTrigger>
346
- <PopoverContent className="w-auto p-0" align="start">
347
- <Calendar
348
- mode="single"
349
- selected={toField.value ? new Date(toField.value) : undefined}
350
- onSelect={(date) => toField.onChange(date?.toISOString())}
351
- initialFocus
352
- />
353
- </PopoverContent>
354
- </Popover>
355
- )}
356
- />
357
- </div>
358
- )}
363
+ </FormControl>
364
+ </PopoverTrigger>
365
+ <PopoverContent className="w-auto p-0" align="start">
366
+ <Calendar
367
+ mode="single"
368
+ selected={field.value ? new Date(field.value) : undefined}
369
+ onSelect={(date) => field.onChange(date?.toISOString())}
370
+ initialFocus
371
+ />
372
+ </PopoverContent>
373
+ </Popover>
374
+
375
+ <FormField
376
+ control={control}
377
+ name="date_service_to"
378
+ render={({ field: toField }) => (
379
+ <Popover>
380
+ <PopoverTrigger asChild>
381
+ <Button
382
+ variant="outline"
383
+ className={cn(
384
+ "w-full pl-3 text-left font-normal",
385
+ !toField.value && "text-muted-foreground",
386
+ )}
387
+ >
388
+ {toField.value ? new Date(toField.value).toLocaleDateString() : <span>{t("To")}</span>}
389
+ <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
390
+ </Button>
391
+ </PopoverTrigger>
392
+ <PopoverContent className="w-auto p-0" align="start">
393
+ <Calendar
394
+ mode="single"
395
+ selected={toField.value ? new Date(toField.value) : undefined}
396
+ onSelect={(date) => toField.onChange(date?.toISOString())}
397
+ initialFocus
398
+ />
399
+ </PopoverContent>
400
+ </Popover>
401
+ )}
402
+ />
403
+ </div>
404
+ )}
405
+ </div>
359
406
  <FormMessage />
360
407
  </FormItem>
361
408
  )}
@@ -368,29 +415,60 @@ export function DocumentDetailsSection({
368
415
  name={dateFieldName}
369
416
  render={({ field }) => (
370
417
  <FormItem>
371
- <FormLabel className="">{dateFieldLabel}</FormLabel>
372
- <Popover>
373
- <PopoverTrigger asChild>
374
- <FormControl>
375
- <Button
376
- variant="outline"
377
- className={cn("w-full pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
378
- >
379
- {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
380
- <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
381
- </Button>
382
- </FormControl>
383
- </PopoverTrigger>
384
- <PopoverContent className="w-auto p-0" align="start">
385
- <Calendar
386
- mode="single"
387
- selected={field.value ? new Date(field.value) : undefined}
388
- onSelect={(date) => field.onChange(date?.toISOString())}
389
- disabled={(date) => date < new Date("1900-01-01")}
390
- initialFocus
391
- />
392
- </PopoverContent>
393
- </Popover>
418
+ <div className="flex items-center gap-3">
419
+ <FormLabel className={LABEL_WIDTH}>{dateFieldLabel}</FormLabel>
420
+ {documentType === "invoice" && dueDays && (
421
+ <Select
422
+ value={String(dueDays.dueDaysType)}
423
+ onValueChange={(v) => dueDays.onDueDaysTypeChange(v === "custom" ? "custom" : Number(v))}
424
+ >
425
+ <SelectTrigger className="h-8 w-auto shrink-0 gap-1 border-none px-2 text-xs shadow-none">
426
+ <SelectValue>
427
+ {dueDays.dueDaysType === "custom"
428
+ ? t("Custom")
429
+ : dueDays.dueDaysType === 0
430
+ ? t("On receipt")
431
+ : t(`${dueDays.dueDaysType} days`)}
432
+ </SelectValue>
433
+ </SelectTrigger>
434
+ <SelectContent>
435
+ {DUE_DAYS_PRESETS.map((days) => (
436
+ <SelectItem key={days} value={String(days)}>
437
+ {days === 0 ? t("On receipt") : t(`${days} days`)}
438
+ </SelectItem>
439
+ ))}
440
+ <SelectItem value="custom">{t("Custom")}</SelectItem>
441
+ </SelectContent>
442
+ </Select>
443
+ )}
444
+ <Popover>
445
+ <PopoverTrigger asChild>
446
+ <FormControl>
447
+ <Button
448
+ variant="outline"
449
+ className={cn("flex-1 pl-3 text-left font-normal", !field.value && "text-muted-foreground")}
450
+ >
451
+ {field.value ? new Date(field.value).toLocaleDateString() : <span>{t("Pick a date")}</span>}
452
+ <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
453
+ </Button>
454
+ </FormControl>
455
+ </PopoverTrigger>
456
+ <PopoverContent className="w-auto p-0" align="start">
457
+ <Calendar
458
+ mode="single"
459
+ selected={field.value ? new Date(field.value) : undefined}
460
+ onSelect={(date) => {
461
+ field.onChange(date?.toISOString());
462
+ if (dueDays && dueDays.dueDaysType !== "custom") {
463
+ dueDays.onDueDaysTypeChange("custom");
464
+ }
465
+ }}
466
+ disabled={(date) => date < new Date("1900-01-01")}
467
+ initialFocus
468
+ />
469
+ </PopoverContent>
470
+ </Popover>
471
+ </div>
394
472
  <FormMessage />
395
473
  </FormItem>
396
474
  )}
@@ -399,24 +477,42 @@ export function DocumentDetailsSection({
399
477
 
400
478
  <FormField
401
479
  control={control}
402
- name="currency_code"
480
+ name="reference"
403
481
  render={({ field }) => (
404
482
  <FormItem>
405
- <FormLabel>{t("Currency")} *</FormLabel>
406
- <Select onValueChange={(value) => value && field.onChange(value)} value={field.value || ""}>
483
+ <div className="flex items-center gap-3">
484
+ <FormLabel className={LABEL_WIDTH}>{t("Reference")}</FormLabel>
407
485
  <FormControl>
408
- <SelectTrigger className="h-10">
409
- <SelectValue placeholder={t("Select currency")} />
410
- </SelectTrigger>
486
+ <Input {...field} value={field.value || ""} placeholder={t("e.g., PO-2024-001")} />
411
487
  </FormControl>
412
- <SelectContent>
413
- {CURRENCY_CODES.map((currency) => (
414
- <SelectItem key={currency.value} value={currency.value}>
415
- {currency.label}
416
- </SelectItem>
417
- ))}
418
- </SelectContent>
419
- </Select>
488
+ </div>
489
+ <FormMessage />
490
+ </FormItem>
491
+ )}
492
+ />
493
+
494
+ <FormField
495
+ control={control}
496
+ name="currency_code"
497
+ render={({ field }) => (
498
+ <FormItem>
499
+ <div className="flex items-center gap-3">
500
+ <FormLabel className={LABEL_WIDTH}>{t("Currency")} *</FormLabel>
501
+ <Select onValueChange={(value) => value && field.onChange(value)} value={field.value || ""}>
502
+ <FormControl>
503
+ <SelectTrigger className="h-10 flex-1">
504
+ <SelectValue placeholder={t("Select currency")} />
505
+ </SelectTrigger>
506
+ </FormControl>
507
+ <SelectContent>
508
+ {CURRENCY_CODES.map((currency) => (
509
+ <SelectItem key={currency.value} value={currency.value}>
510
+ {currency.label}
511
+ </SelectItem>
512
+ ))}
513
+ </SelectContent>
514
+ </Select>
515
+ </div>
420
516
  <FormMessage />
421
517
  </FormItem>
422
518
  )}
@@ -432,123 +528,194 @@ export function DocumentDetailsSection({
432
528
  * Note field component with smart code insertion button
433
529
  * Exported for use in document forms (placed after items section)
434
530
  */
435
- // Helper functions for template variable replacement (shared with InputWithPreview)
436
- function formatVariableName(varName: string): string {
437
- return varName
438
- .split("_")
439
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
440
- .join(" ");
441
- }
531
+ export function DocumentNoteField({
532
+ control,
533
+ t,
534
+ entity,
535
+ document,
536
+ }: {
537
+ control: AnyControl;
538
+ t: (key: string) => string;
539
+ entity?: Entity | null;
540
+ document?: Partial<Invoice | Estimate> | null;
541
+ }) {
542
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
543
+ const [isFocused, setIsFocused] = useState(false);
442
544
 
443
- function getVariableValue(
444
- varName: string,
445
- entity?: Entity | null,
446
- document?: Partial<Invoice | Estimate> | null,
447
- ): string | null {
448
- if (!entity) return null;
449
-
450
- // Entity-related variables
451
- if (varName === "entity_name") return entity.name || null;
452
- if (varName === "entity_email") return (entity.settings as any)?.email || null;
453
-
454
- // Date variables
455
- if (varName === "current_date") {
456
- return new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
457
- }
458
- if (varName === "current_year") return new Date().getFullYear().toString();
459
-
460
- // Document-specific variables
461
- if (document) {
462
- if (varName === "document_number") return (document as any).number || null;
463
- if (varName === "document_date" && (document as any).date) {
464
- return new Date((document as any).date).toLocaleDateString("en-US", {
465
- month: "long",
466
- day: "numeric",
467
- year: "numeric",
468
- });
469
- }
470
- if (varName === "document_total" && (document as any).total_with_tax) {
471
- return new Intl.NumberFormat("en-US", {
472
- style: "currency",
473
- currency: (document as any).currency_code || "USD",
474
- }).format(Number((document as any).total_with_tax));
475
- }
476
- if (varName === "document_currency") return (document as any).currency_code || null;
477
-
478
- // Invoice due date
479
- if (varName === "document_due_date" && (document as any).date_due) {
480
- return new Date((document as any).date_due).toLocaleDateString("en-US", {
481
- month: "long",
482
- day: "numeric",
483
- year: "numeric",
484
- });
485
- }
486
-
487
- // Estimate valid until
488
- if (varName === "document_valid_until" && (document as any).date_valid_till) {
489
- return new Date((document as any).date_valid_till).toLocaleDateString("en-US", {
490
- month: "long",
491
- day: "numeric",
492
- year: "numeric",
493
- });
494
- }
495
-
496
- // Customer variables
497
- if ((document as any).customer) {
498
- if (varName === "customer_name") return (document as any).customer.name || null;
499
- if (varName === "customer_email") return (document as any).customer.email || null;
500
- }
501
- }
502
-
503
- return null;
545
+ return (
546
+ <FormField
547
+ control={control}
548
+ name="note"
549
+ render={({ field }) => {
550
+ const hasContent = field.value;
551
+ const showPreview = !isFocused && hasContent && entity;
552
+ const preview = showPreview ? replaceTemplateVariablesForPreview(field.value || "", entity, document) : null;
553
+
554
+ return (
555
+ <FormItem>
556
+ <div className="flex items-center justify-between">
557
+ <FormLabel>{t("Note")}</FormLabel>
558
+ <SmartCodeInsertButton
559
+ textareaRef={textareaRef}
560
+ value={field.value || ""}
561
+ onInsert={(newValue) => field.onChange(newValue)}
562
+ t={t}
563
+ />
564
+ </div>
565
+ <FormControl>
566
+ <div className="relative">
567
+ <Textarea
568
+ {...field}
569
+ ref={(e) => {
570
+ field.ref(e);
571
+ (textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = e;
572
+ }}
573
+ value={field.value || ""}
574
+ placeholder={showPreview ? "" : t("Add payment instructions, terms, or other notes...")}
575
+ rows={5}
576
+ className={cn("resize-y", showPreview && "text-transparent caret-transparent")}
577
+ onFocus={() => setIsFocused(true)}
578
+ onBlur={() => setIsFocused(false)}
579
+ />
580
+ {showPreview && (
581
+ <div className="pointer-events-none absolute inset-0 z-10 flex items-start overflow-hidden rounded-md border border-input bg-background px-3 py-2 shadow-xs">
582
+ <div className="w-full whitespace-pre-wrap text-base md:text-sm">{preview}</div>
583
+ </div>
584
+ )}
585
+ </div>
586
+ </FormControl>
587
+ <FormMessage />
588
+ </FormItem>
589
+ );
590
+ }}
591
+ />
592
+ );
504
593
  }
505
594
 
506
- function replaceTemplateVariablesForPreview(
507
- template: string,
508
- entity?: Entity | null,
509
- document?: Partial<Invoice | Estimate> | null,
510
- ): React.ReactNode[] {
511
- if (!template) return [];
512
-
513
- const parts: React.ReactNode[] = [];
514
- const regex = /\{([^}]+)\}/g;
515
- let lastIndex = 0;
516
- let match: RegExpExecArray | null = null;
517
-
518
- match = regex.exec(template);
519
- while (match !== null) {
520
- if (match.index > lastIndex) {
521
- parts.push(template.slice(lastIndex, match.index));
522
- }
523
-
524
- const varName = match[1];
525
- const actualValue = getVariableValue(varName, entity, document);
526
- const displayValue = actualValue || formatVariableName(varName);
527
-
528
- parts.push(
529
- <span
530
- key={match.index}
531
- className={cn(
532
- "rounded px-1.5 py-0.5 font-medium text-xs",
533
- actualValue ? "bg-secondary text-secondary-foreground" : "bg-primary/10 text-primary",
534
- )}
535
- >
536
- {displayValue}
537
- </span>,
538
- );
595
+ /**
596
+ * Tax clause field component with smart code insertion button
597
+ * Similar to DocumentNoteField, auto-populated from entity settings based on transaction type
598
+ */
599
+ type TransactionType = ViesCheckResponse["transaction_type"];
600
+
601
+ const TRANSACTION_TYPE_LABELS: Record<NonNullable<TransactionType>, string> = {
602
+ domestic: "Domestic",
603
+ intra_eu_b2b: "EU B2B",
604
+ intra_eu_b2c: "EU B2C",
605
+ export: "Export",
606
+ };
607
+
608
+ const TRANSACTION_TYPE_VARIANTS: Record<NonNullable<TransactionType>, "secondary" | "default" | "outline"> = {
609
+ domestic: "secondary",
610
+ intra_eu_b2b: "default",
611
+ intra_eu_b2c: "outline",
612
+ export: "outline",
613
+ };
614
+
615
+ export function DocumentTaxClauseField({
616
+ control,
617
+ t,
618
+ entity,
619
+ document,
620
+ transactionType,
621
+ isTransactionTypeFetching,
622
+ isFinaNonDomestic,
623
+ }: {
624
+ control: AnyControl;
625
+ t: (key: string) => string;
626
+ entity?: Entity | null;
627
+ document?: Partial<Invoice | Estimate> | null;
628
+ transactionType?: TransactionType;
629
+ isTransactionTypeFetching?: boolean;
630
+ isFinaNonDomestic?: boolean;
631
+ }) {
632
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
633
+ const [isFocused, setIsFocused] = useState(false);
539
634
 
540
- lastIndex = regex.lastIndex;
541
- match = regex.exec(template);
542
- }
635
+ const effectiveTransactionType = transactionType ?? "domestic";
636
+ const showTransactionInfo = true;
543
637
 
544
- if (lastIndex < template.length) {
545
- parts.push(template.slice(lastIndex));
546
- }
638
+ return (
639
+ <FormField
640
+ control={control}
641
+ name="tax_clause"
642
+ render={({ field }) => {
643
+ const hasContent = field.value;
644
+ const showPreview = !isFocused && hasContent && entity;
645
+ const preview = showPreview ? replaceTemplateVariablesForPreview(field.value || "", entity, document) : null;
547
646
 
548
- return parts;
647
+ return (
648
+ <FormItem>
649
+ <div className="flex items-center justify-between">
650
+ <FormLabel>{t("Tax Clause")}</FormLabel>
651
+ <SmartCodeInsertButton
652
+ textareaRef={textareaRef}
653
+ value={field.value || ""}
654
+ onInsert={(newValue) => field.onChange(newValue)}
655
+ t={t}
656
+ />
657
+ </div>
658
+ {showTransactionInfo && (
659
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 rounded-md border border-border/50 bg-muted/30 px-2.5 py-1.5 text-sm">
660
+ {isTransactionTypeFetching ? (
661
+ <div className="flex items-center gap-1.5 text-muted-foreground">
662
+ <Loader2 className="size-3.5 animate-spin" />
663
+ <span>{t("Determining transaction type...")}</span>
664
+ </div>
665
+ ) : (
666
+ <>
667
+ <div className="flex items-center gap-1.5">
668
+ <Globe className="size-3.5 text-muted-foreground" />
669
+ <span className="text-muted-foreground">{t("Transaction type")}:</span>
670
+ <Badge variant={TRANSACTION_TYPE_VARIANTS[effectiveTransactionType]}>
671
+ {t(TRANSACTION_TYPE_LABELS[effectiveTransactionType])}
672
+ </Badge>
673
+ </div>
674
+ {isFinaNonDomestic && (
675
+ <div className="flex items-center gap-1.5 text-muted-foreground">
676
+ <Info className="size-3.5" />
677
+ <span>{t("This invoice will not be fiscalized (non-domestic transaction)")}</span>
678
+ </div>
679
+ )}
680
+ </>
681
+ )}
682
+ </div>
683
+ )}
684
+ <FormControl>
685
+ <div className="relative">
686
+ <Textarea
687
+ {...field}
688
+ ref={(e) => {
689
+ field.ref(e);
690
+ (textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = e;
691
+ }}
692
+ value={field.value || ""}
693
+ placeholder={showPreview ? "" : t("Add tax clause...")}
694
+ rows={3}
695
+ className={cn("resize-y", showPreview && "text-transparent caret-transparent")}
696
+ onFocus={() => setIsFocused(true)}
697
+ onBlur={() => setIsFocused(false)}
698
+ />
699
+ {showPreview && (
700
+ <div className="pointer-events-none absolute inset-0 z-10 flex items-start overflow-hidden rounded-md border border-input bg-background px-3 py-2 shadow-xs">
701
+ <div className="w-full whitespace-pre-wrap text-base md:text-sm">{preview}</div>
702
+ </div>
703
+ )}
704
+ </div>
705
+ </FormControl>
706
+ <FormMessage />
707
+ </FormItem>
708
+ );
709
+ }}
710
+ />
711
+ );
549
712
  }
550
713
 
551
- export function DocumentNoteField({
714
+ /**
715
+ * Signature field component with smart code insertion button
716
+ * Wrapped in a collapsible for a clean form layout
717
+ */
718
+ export function DocumentSignatureField({
552
719
  control,
553
720
  t,
554
721
  entity,
@@ -565,7 +732,7 @@ export function DocumentNoteField({
565
732
  return (
566
733
  <FormField
567
734
  control={control}
568
- name="note"
735
+ name="signature"
569
736
  render={({ field }) => {
570
737
  const hasContent = field.value;
571
738
  const showPreview = !isFocused && hasContent && entity;
@@ -574,7 +741,7 @@ export function DocumentNoteField({
574
741
  return (
575
742
  <FormItem>
576
743
  <div className="flex items-center justify-between">
577
- <FormLabel>{t("Note")}</FormLabel>
744
+ <FormLabel>{t("Signature")}</FormLabel>
578
745
  <SmartCodeInsertButton
579
746
  textareaRef={textareaRef}
580
747
  value={field.value || ""}
@@ -591,8 +758,8 @@ export function DocumentNoteField({
591
758
  (textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = e;
592
759
  }}
593
760
  value={field.value || ""}
594
- placeholder={showPreview ? "" : t("Add payment instructions, terms, or other notes...")}
595
- rows={5}
761
+ placeholder={showPreview ? "" : t("Add signature text...")}
762
+ rows={2}
596
763
  className={cn("resize-y", showPreview && "text-transparent caret-transparent")}
597
764
  onFocus={() => setIsFocused(true)}
598
765
  onBlur={() => setIsFocused(false)}
@@ -679,3 +846,82 @@ export function DocumentPaymentTermsField({
679
846
  />
680
847
  );
681
848
  }
849
+
850
+ /**
851
+ * Footer field component with collapsible wrapper and smart code insertion button
852
+ * Collapsed by default, opens if content exists
853
+ */
854
+ export function DocumentFooterField({
855
+ control,
856
+ t,
857
+ entity,
858
+ document,
859
+ }: {
860
+ control: AnyControl;
861
+ t: (key: string) => string;
862
+ entity?: Entity | null;
863
+ document?: Partial<Invoice | Estimate> | null;
864
+ }) {
865
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
866
+ const [isFocused, setIsFocused] = useState(false);
867
+
868
+ return (
869
+ <FormField
870
+ control={control}
871
+ name="footer"
872
+ render={({ field }) => {
873
+ const hasContent = field.value;
874
+ const showPreview = !isFocused && hasContent && entity;
875
+ const preview = showPreview ? replaceTemplateVariablesForPreview(field.value || "", entity, document) : null;
876
+
877
+ return (
878
+ <FormItem>
879
+ <Collapsible defaultOpen={!!hasContent}>
880
+ <div className="flex items-center justify-between">
881
+ <CollapsibleTrigger asChild>
882
+ <button type="button" className="flex items-center gap-1 font-medium text-sm">
883
+ <ChevronDown className="size-4 transition-transform [[data-panel-hidden]_&]:-rotate-90 [[data-panel-open]_&]:rotate-0" />
884
+ {t("Footer")}
885
+ </button>
886
+ </CollapsibleTrigger>
887
+ <div className="[[data-panel-hidden]_&]:hidden">
888
+ <SmartCodeInsertButton
889
+ textareaRef={textareaRef}
890
+ value={field.value || ""}
891
+ onInsert={(newValue) => field.onChange(newValue)}
892
+ t={t}
893
+ />
894
+ </div>
895
+ </div>
896
+ <CollapsibleContent className="mt-2">
897
+ <FormControl>
898
+ <div className="relative">
899
+ <Textarea
900
+ {...field}
901
+ ref={(e) => {
902
+ field.ref(e);
903
+ (textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = e;
904
+ }}
905
+ value={field.value || ""}
906
+ placeholder={showPreview ? "" : t("Add document footer...")}
907
+ rows={2}
908
+ className={cn("resize-y", showPreview && "text-transparent caret-transparent")}
909
+ onFocus={() => setIsFocused(true)}
910
+ onBlur={() => setIsFocused(false)}
911
+ />
912
+ {showPreview && (
913
+ <div className="pointer-events-none absolute inset-0 z-10 flex items-start overflow-hidden rounded-md border border-input bg-background px-3 py-2 shadow-xs">
914
+ <div className="w-full whitespace-pre-wrap text-base md:text-sm">{preview}</div>
915
+ </div>
916
+ )}
917
+ </div>
918
+ </FormControl>
919
+ <FormMessage />
920
+ </CollapsibleContent>
921
+ </Collapsible>
922
+ </FormItem>
923
+ );
924
+ }}
925
+ />
926
+ );
927
+ }