@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.
- package/cli/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/advance-invoices/advance-invoices.hooks.ts +2 -2
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +151 -45
- package/src/components/advance-invoices/create/locales/de.ts +20 -0
- package/src/components/advance-invoices/create/locales/es.ts +20 -0
- package/src/components/advance-invoices/create/locales/fr.ts +20 -0
- package/src/components/advance-invoices/create/locales/hr.ts +20 -0
- package/src/components/advance-invoices/create/locales/it.ts +20 -0
- package/src/components/advance-invoices/create/locales/nl.ts +20 -0
- package/src/components/advance-invoices/create/locales/pl.ts +20 -0
- package/src/components/advance-invoices/create/locales/pt.ts +20 -0
- package/src/components/advance-invoices/create/locales/sl.ts +20 -0
- package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +5 -5
- package/src/components/advance-invoices/list/list-row-actions.tsx +48 -1
- package/src/components/advance-invoices/list/list-table.tsx +21 -1
- package/src/components/advance-invoices/list/locales/de.ts +4 -0
- package/src/components/advance-invoices/list/locales/en.ts +3 -0
- package/src/components/advance-invoices/list/locales/es.ts +4 -0
- package/src/components/advance-invoices/list/locales/fr.ts +4 -0
- package/src/components/advance-invoices/list/locales/hr.ts +3 -0
- package/src/components/advance-invoices/list/locales/it.ts +4 -0
- package/src/components/advance-invoices/list/locales/nl.ts +4 -0
- package/src/components/advance-invoices/list/locales/pl.ts +3 -0
- package/src/components/advance-invoices/list/locales/pt.ts +4 -0
- package/src/components/advance-invoices/list/locales/sl.ts +3 -0
- package/src/components/credit-notes/create/create-credit-note-form.tsx +161 -42
- package/src/components/credit-notes/create/locales/de.ts +21 -0
- package/src/components/credit-notes/create/locales/es.ts +21 -0
- package/src/components/credit-notes/create/locales/fr.ts +21 -0
- package/src/components/credit-notes/create/locales/hr.ts +21 -0
- package/src/components/credit-notes/create/locales/it.ts +21 -0
- package/src/components/credit-notes/create/locales/nl.ts +21 -0
- package/src/components/credit-notes/create/locales/pl.ts +21 -0
- package/src/components/credit-notes/create/locales/pt.ts +21 -0
- package/src/components/credit-notes/create/locales/sl.ts +22 -1
- package/src/components/credit-notes/credit-notes.hooks.ts +2 -2
- package/src/components/credit-notes/list/list-row-actions.tsx +44 -1
- package/src/components/credit-notes/list/list-table.tsx +16 -2
- package/src/components/credit-notes/list/locales/de.ts +2 -0
- package/src/components/credit-notes/list/locales/en.ts +2 -0
- package/src/components/credit-notes/list/locales/es.ts +2 -0
- package/src/components/credit-notes/list/locales/fr.ts +2 -0
- package/src/components/credit-notes/list/locales/hr.ts +2 -0
- package/src/components/credit-notes/list/locales/it.ts +2 -0
- package/src/components/credit-notes/list/locales/nl.ts +2 -0
- package/src/components/credit-notes/list/locales/pl.ts +2 -0
- package/src/components/credit-notes/list/locales/pt.ts +2 -0
- package/src/components/credit-notes/list/locales/sl.ts +2 -0
- package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -9
- package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +77 -48
- package/src/components/dashboard/shared/use-revenue-data.ts +77 -9
- package/src/components/delivery-notes/create/create-delivery-note-form.tsx +114 -7
- package/src/components/delivery-notes/create/locales/de.ts +21 -0
- package/src/components/delivery-notes/create/locales/es.ts +21 -0
- package/src/components/delivery-notes/create/locales/fr.ts +21 -0
- package/src/components/delivery-notes/create/locales/hr.ts +21 -0
- package/src/components/delivery-notes/create/locales/it.ts +21 -0
- package/src/components/delivery-notes/create/locales/nl.ts +21 -0
- package/src/components/delivery-notes/create/locales/pl.ts +21 -0
- package/src/components/delivery-notes/create/locales/pt.ts +21 -0
- package/src/components/delivery-notes/create/locales/sl.ts +22 -1
- package/src/components/delivery-notes/list/list-table.tsx +17 -8
- package/src/components/delivery-notes/list/locales/de.ts +32 -1
- package/src/components/delivery-notes/list/locales/en.ts +31 -0
- package/src/components/delivery-notes/list/locales/es.ts +32 -1
- package/src/components/delivery-notes/list/locales/fr.ts +32 -1
- package/src/components/delivery-notes/list/locales/hr.ts +32 -1
- package/src/components/delivery-notes/list/locales/it.ts +32 -1
- package/src/components/delivery-notes/list/locales/nl.ts +32 -1
- package/src/components/delivery-notes/list/locales/pl.ts +32 -1
- package/src/components/delivery-notes/list/locales/pt.ts +32 -1
- package/src/components/delivery-notes/list/locales/sl.ts +32 -1
- package/src/components/documents/create/document-add-item-form.tsx +70 -0
- package/src/components/documents/create/document-details-section.tsx +590 -344
- package/src/components/documents/create/document-items-section.tsx +21 -4
- package/src/components/documents/create/live-preview.tsx +24 -4
- package/src/components/documents/create/mark-as-paid-section.tsx +29 -20
- package/src/components/documents/create/prepare-document-submission.ts +22 -8
- package/src/components/documents/create/smart-code-insert-button.tsx +6 -0
- package/src/components/documents/shared/document-preview-display.tsx +3 -4
- package/src/components/documents/view/document-actions-bar.tsx +7 -27
- package/src/components/documents/view/document-details-card.tsx +32 -9
- package/src/components/documents/view/locales/de.ts +3 -0
- package/src/components/documents/view/locales/es.ts +3 -0
- package/src/components/documents/view/locales/fr.ts +3 -0
- package/src/components/documents/view/locales/hr.ts +3 -0
- package/src/components/documents/view/locales/it.ts +3 -0
- package/src/components/documents/view/locales/nl.ts +3 -0
- package/src/components/documents/view/locales/pl.ts +3 -0
- package/src/components/documents/view/locales/pt.ts +3 -0
- package/src/components/documents/view/locales/sl.ts +4 -1
- package/src/components/documents/view/use-document-download.ts +6 -3
- package/src/components/entities/create-entity-form.tsx +2 -1
- package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +6 -0
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +2 -117
- package/src/components/entities/entity-settings-form/locales/de.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/es.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/fr.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/hr.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/it.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/nl.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/pl.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/pt.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +5 -0
- package/src/components/entities/fina-settings-form/fina-settings-form.tsx +15 -0
- package/src/components/entities/fina-settings-form/fina-settings.hooks.ts +5 -1
- 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/fina-settings-form/sections/premises-management-section.tsx +4 -4
- package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +3 -3
- package/src/components/entities/settings/company-settings-form.tsx +173 -20
- package/src/components/entities/settings/defaults-settings-form.tsx +38 -1
- package/src/components/entities/settings/tax-rules-settings-form.tsx +1 -2
- package/src/components/estimates/create/create-estimate-form.tsx +107 -8
- package/src/components/estimates/create/locales/de.ts +21 -0
- package/src/components/estimates/create/locales/es.ts +21 -0
- package/src/components/estimates/create/locales/fr.ts +21 -0
- package/src/components/estimates/create/locales/hr.ts +21 -0
- package/src/components/estimates/create/locales/it.ts +21 -0
- package/src/components/estimates/create/locales/nl.ts +21 -0
- package/src/components/estimates/create/locales/pl.ts +21 -0
- package/src/components/estimates/create/locales/pt.ts +21 -0
- package/src/components/estimates/create/locales/sl.ts +22 -1
- package/src/components/estimates/list/list-table.tsx +11 -2
- package/src/components/estimates/list/locales/de.ts +1 -0
- package/src/components/estimates/list/locales/en.ts +1 -0
- package/src/components/estimates/list/locales/es.ts +1 -0
- package/src/components/estimates/list/locales/fr.ts +1 -0
- package/src/components/estimates/list/locales/hr.ts +1 -0
- package/src/components/estimates/list/locales/it.ts +1 -0
- package/src/components/estimates/list/locales/nl.ts +1 -0
- package/src/components/estimates/list/locales/pl.ts +1 -0
- package/src/components/estimates/list/locales/pt.ts +1 -0
- package/src/components/estimates/list/locales/sl.ts +1 -0
- package/src/components/export/document-export-form.tsx +46 -12
- package/src/components/invoices/create/create-invoice-form.tsx +186 -48
- package/src/components/invoices/create/locales/de.ts +28 -0
- package/src/components/invoices/create/locales/es.ts +28 -0
- package/src/components/invoices/create/locales/fr.ts +28 -0
- package/src/components/invoices/create/locales/hr.ts +28 -0
- package/src/components/invoices/create/locales/it.ts +28 -0
- package/src/components/invoices/create/locales/nl.ts +28 -0
- package/src/components/invoices/create/locales/pl.ts +28 -0
- package/src/components/invoices/create/locales/pt.ts +28 -0
- package/src/components/invoices/create/locales/sl.ts +29 -1
- package/src/components/invoices/create/prepare-invoice-submission.ts +5 -5
- package/src/components/invoices/invoices.hooks.ts +2 -2
- package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
- package/src/components/table/search-input.tsx +17 -0
- package/src/components/table/table-pagination.tsx +1 -1
- package/src/generated/schemas/advanceinvoice.ts +6 -2
- package/src/generated/schemas/creditnote.ts +3 -1
- package/src/generated/schemas/deliverynote.ts +3 -1
- package/src/generated/schemas/entity.ts +4 -4
- package/src/generated/schemas/entityapikey.ts +19 -0
- package/src/generated/schemas/estimate.ts +6 -2
- package/src/generated/schemas/index.ts +1 -0
- package/src/generated/schemas/invoice.ts +4 -1
- package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +17 -23
- package/src/generated/schemas/rendercreditnotepreview_body.ts +17 -23
- package/src/generated/schemas/renderdeliverynotepreview_body.ts +17 -23
- package/src/generated/schemas/renderestimatepreview_body.ts +17 -23
- package/src/generated/schemas/renderinvoicepreview_body.ts +19 -23
- package/src/generated/schemas/startpdfexport_body.ts +14 -2
- package/src/generated/schemas/webhook.ts +4 -0
- package/src/hooks/use-duplicate-document.ts +16 -9
- package/src/hooks/use-vies-check.ts +3 -0
- package/src/lib/template-variables.tsx +167 -0
- 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
|
-
|
|
47
|
+
business_premise_name: string;
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
type FinaDevice = {
|
|
48
51
|
id: string;
|
|
49
|
-
|
|
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-
|
|
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
|
|
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
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{premise.business_premise_name}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{device.electronic_device_name}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
))}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
<
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<
|
|
220
|
-
<
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
<
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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 -
|
|
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
|
|
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
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
{
|
|
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
|
-
</
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
</
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
<
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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="
|
|
480
|
+
name="reference"
|
|
403
481
|
render={({ field }) => (
|
|
404
482
|
<FormItem>
|
|
405
|
-
<
|
|
406
|
-
|
|
483
|
+
<div className="flex items-center gap-3">
|
|
484
|
+
<FormLabel className={LABEL_WIDTH}>{t("Reference")}</FormLabel>
|
|
407
485
|
<FormControl>
|
|
408
|
-
<
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
635
|
+
const effectiveTransactionType = transactionType ?? "domestic";
|
|
636
|
+
const showTransactionInfo = true;
|
|
543
637
|
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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("
|
|
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
|
|
595
|
-
rows={
|
|
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
|
+
}
|