Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/@accounter_client-3961-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@accounter/client": patch
---
dependencies updates:
- Updated dependency [`radix-ui@1.6.4` ↗︎](https://www.npmjs.com/package/radix-ui/v/1.6.4) (from `1.6.2`, in `dependencies`)
- Updated dependency [`react-hook-form@7.82.0` ↗︎](https://www.npmjs.com/package/react-hook-form/v/7.82.0) (from `7.81.0`, in `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@accounter_server-3961-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@accounter/server": patch
---
dependencies updates:
- Updated dependency [`@ai-sdk/anthropic@4.0.16` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.16) (from `4.0.15`, in `dependencies`)
- Updated dependency [`@graphql-tools/utils@11.2.2` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/11.2.2) (from `11.2.0`, in `dependencies`)
- Updated dependency [`ai@7.0.31` ↗︎](https://www.npmjs.com/package/ai/v/7.0.31) (from `7.0.29`, in `dependencies`)
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"react-dnd": "16.0.1",
"react-dom": "19.2.7",
"react-error-boundary": "6.1.2",
"react-hook-form": "7.81.0",
"react-hook-form": "7.82.0",
"react-number-format": "5.4.5",
"react-router-dom": "7.18.1",
"react-to-pdf": "3.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { useEffect, useState, type ReactElement } from 'react';
import { ListPlus, Trash2 } from 'lucide-react';
import {
Controller,
useFieldArray,
type ArrayPath,
type FieldArray,
type FieldValues,
type Path,
type UseFormReturn,
} from 'react-hook-form';
import { useQuery } from 'urql';
import { NumberInput, Select } from '@mantine/core';
import { AttendeesByBusinessTripDocument } from '../../../../gql/graphql.js';
import {
useControlledFieldArray,
type FieldArrayItem,
} from '../../../../hooks/use-controlled-field-array.js';
import { Button } from '../../../ui/button.js';

type Props<T extends FieldValues> = {
Expand All @@ -32,22 +34,14 @@ export function AttendeesStayInput<T extends FieldValues>({
fetchingAttendees = false,
businessTripId,
}: Props<T>): ReactElement {
const { control, watch, trigger } = formManager;
const { fields, append, remove } = useFieldArray({
control,
name: attendeesStayPath,
});
const { control, trigger } = formManager;
const { controlledFields, append, remove } = useControlledFieldArray(
formManager,
attendeesStayPath,
);
const [fetching, setFetching] = useState(fetchingAttendees);
const [attendees, setAttendees] = useState(attendeesData ?? []);

const watchFieldArray = watch(attendeesStayPath as Path<T>);
const controlledFields = fields.map((field, index) => {
return {
...field,
...watchFieldArray[index],
};
});

useEffect(() => {
if (attendeesData) {
setAttendees(attendeesData);
Expand Down Expand Up @@ -169,7 +163,7 @@ export function AttendeesStayInput<T extends FieldValues>({
size="icon"
className="size-7.5"
onClick={(): void => {
append({} as FieldArray<T, ArrayPath<T>>);
append({} as FieldArrayItem<T>);
trigger(attendeesStayPath as Path<T>);
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { ReactElement } from 'react';
import { ListPlus, Trash2 } from 'lucide-react';
import { type ArrayPath, type FieldValues, type Path, type UseFormReturn } from 'react-hook-form';
import {
useFieldArray,
type ArrayPath,
type FieldArray,
type FieldValues,
type Path,
type UseFormReturn,
} from 'react-hook-form';
useControlledFieldArray,
type FieldArrayItem,
} from '../../../../hooks/use-controlled-field-array.js';
import { Button } from '../../../ui/button.js';
import { FormControl, FormField, FormItem, FormMessage } from '../../../ui/form';
import { Input } from '../../../ui/input';
Expand All @@ -24,19 +21,11 @@ export function FlightPathInput<T extends FieldValues>({
flightPathPath,
flightPathData,
}: Props<T>): ReactElement {
const { control, watch, trigger } = formManager;
const { fields, append, remove } = useFieldArray({
control,
name: flightPathPath as ArrayPath<T>,
});

const watchFieldArray = watch(flightPathPath);
const controlledFields = fields.map((field, index) => {
return {
...field,
...watchFieldArray[index],
};
});
const { control, trigger } = formManager;
const { controlledFields, append, remove } = useControlledFieldArray(
formManager,
flightPathPath as ArrayPath<T>,
);

if (!flightPathData) {
return <div>Cannot edit flight path stay, lacking some mandatory information!</div>;
Expand Down Expand Up @@ -87,7 +76,7 @@ export function FlightPathInput<T extends FieldValues>({
size="icon"
className="size-7.5"
onClick={(): void => {
append('' as FieldArray<T, ArrayPath<T>>);
append('' as FieldArrayItem<T>);
trigger(flightPathPath);
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { format } from 'date-fns';
import { ListPlus, Trash2 } from 'lucide-react';
import {
Controller,
useFieldArray,
type ArrayPath,
type FieldArray,
type FieldValues,
type Path,
type UseFormReturn,
} from 'react-hook-form';
import { NumberInput } from '@mantine/core';
import { YearPickerInput } from '@mantine/dates';
import {
useControlledFieldArray,
type FieldArrayItem,
} from '../../../hooks/use-controlled-field-array.js';
import { Button } from '../../ui/button.js';

type Props<T extends FieldValues> = {
Expand All @@ -23,19 +25,11 @@ export function ChargeSpreadInput<T extends FieldValues>({
formManager,
chargeSpreadPath,
}: Props<T>): ReactElement {
const { control, watch, trigger } = formManager;
const { fields, append, remove } = useFieldArray({
control,
name: chargeSpreadPath,
});

const watchFieldArray = watch(chargeSpreadPath as Path<T>);
const controlledFields = fields.map((field, index) => {
return {
...field,
...watchFieldArray[index],
};
});
const { control, trigger } = formManager;
const { controlledFields, append, remove, watchFieldArray } = useControlledFieldArray(
formManager,
chargeSpreadPath,
);

return (
<div>
Expand All @@ -50,14 +44,13 @@ export function ChargeSpreadInput<T extends FieldValues>({
rules={{
required: 'Required',
validate: (value: string): boolean | string => {
const years = watchFieldArray.map((record: { year: string }) => record.year);
if (years.filter((year: string) => year === value).length > 1) {
const years = watchFieldArray.map(record => (record as { year?: string }).year);
if (years.filter(year => year === value).length > 1) {
return 'Years must be unique';
}
return true;
},
}}
// defaultValue={`${new Date().getFullYear()}-01-01` as PathValue<T, Path<T>>}
render={({ field: { value, ...field }, fieldState }): ReactElement => {
return (
<YearPickerInput
Expand Down Expand Up @@ -116,7 +109,7 @@ export function ChargeSpreadInput<T extends FieldValues>({
size="icon"
className="size-7.5"
onClick={(): void => {
append({ year: `${new Date().getFullYear()}-01-01` } as FieldArray<T, ArrayPath<T>>);
append({ year: `${new Date().getFullYear()}-01-01` } as FieldArrayItem<T>);
trigger(chargeSpreadPath as Path<T>);
}}
>
Expand Down
31 changes: 10 additions & 21 deletions packages/client/src/components/common/inputs/string-array-input.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { ReactElement } from 'react';
import { ListPlus, Trash2 } from 'lucide-react';
import { type ArrayPath, type FieldValues, type Path, type UseFormReturn } from 'react-hook-form';
import {
useFieldArray,
type ArrayPath,
type FieldArray,
type FieldValues,
type Path,
type UseFormReturn,
} from 'react-hook-form';
useControlledFieldArray,
type FieldArrayItem,
} from '../../../hooks/use-controlled-field-array.js';
import { Button } from '../../ui/button.js';
import { FormControl, FormField, FormItem, FormMessage } from '../../ui/form.js';
import { Input } from '../../ui/input.js';
Expand All @@ -23,19 +20,11 @@ export function StringArrayInput<T extends FieldValues>({
formManager,
arrayPath,
}: Props<T>): ReactElement {
const { control, watch } = formManager;
const { fields, append, remove } = useFieldArray({
control,
name: arrayPath as ArrayPath<T>,
});

const watchStringsArray = watch(arrayPath);
const controlledFields = fields.map((field, index) => {
return {
id: field.id,
...watchStringsArray[index],
};
});
const { control } = formManager;
const { controlledFields, append, remove } = useControlledFieldArray(
formManager,
arrayPath as ArrayPath<T>,
);

return (
<div>
Expand Down Expand Up @@ -82,7 +71,7 @@ export function StringArrayInput<T extends FieldValues>({
variant="ghost"
size="icon"
className="size-7.5"
onClick={() => append(undefined as FieldArray<T>)}
onClick={() => append(undefined as FieldArrayItem<T>)}
>
<ListPlus className="size-5" />
</Button>
Expand Down
58 changes: 58 additions & 0 deletions packages/client/src/hooks/use-controlled-field-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
useFieldArray,
type ArrayPath,
type FieldArrayPathValue,
type FieldValues,
type Path,
type UseFieldArrayReturn,
type UseFormReturn,
} from 'react-hook-form';

/**
* Item type of the field array at the given path. Reconstruction of react-hook-form's
* `FieldArray` type, which is shadowed since v7.82 by the new `FieldArray` component
* and can no longer be imported from the package root.
*/
export type FieldArrayItem<
TFieldValues extends FieldValues,
TPath extends ArrayPath<TFieldValues> = ArrayPath<TFieldValues>,
> =
FieldArrayPathValue<TFieldValues, TPath> extends ReadonlyArray<infer TItem> | null | undefined
? TItem
: never;

type UseControlledFieldArrayReturn<
TFieldValues extends FieldValues,
TPath extends ArrayPath<TFieldValues>,
> = Pick<UseFieldArrayReturn<TFieldValues, TPath>, 'append' | 'remove'> & {
controlledFields: UseFieldArrayReturn<TFieldValues, TPath>['fields'];
watchFieldArray: FieldArrayItem<TFieldValues, TPath>[];
};

/**
* Wraps `useFieldArray` and merges the registered fields with the watched values,
* so consumers render always-current values while keeping stable field ids as keys.
*/
export function useControlledFieldArray<
TFieldValues extends FieldValues,
TPath extends ArrayPath<TFieldValues> = ArrayPath<TFieldValues>,
>(
formManager: UseFormReturn<TFieldValues, unknown>,
arrayPath: TPath,
): UseControlledFieldArrayReturn<TFieldValues, TPath> {
const { control, watch } = formManager;
const { fields, append, remove } = useFieldArray({ control, name: arrayPath });

const watchFieldArray = (watch(arrayPath as unknown as Path<TFieldValues>) ??
[]) as FieldArrayItem<TFieldValues, TPath>[];

const controlledFields = fields.map((field, index) => {
const watchedValue = watchFieldArray[index];
return {
...field,
...(typeof watchedValue === 'object' && watchedValue !== null ? watchedValue : {}),
};
});

return { controlledFields, append, remove, watchFieldArray };
}
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ __metadata:
react-dnd: "npm:16.0.1"
react-dom: "npm:19.2.7"
react-error-boundary: "npm:6.1.2"
react-hook-form: "npm:7.81.0"
react-hook-form: "npm:7.82.0"
react-number-format: "npm:5.4.5"
react-router-dom: "npm:7.18.1"
react-to-pdf: "npm:3.2.2"
Expand Down Expand Up @@ -22413,12 +22413,12 @@ __metadata:
languageName: node
linkType: hard

"react-hook-form@npm:7.81.0":
version: 7.81.0
resolution: "react-hook-form@npm:7.81.0"
"react-hook-form@npm:7.82.0":
version: 7.82.0
resolution: "react-hook-form@npm:7.82.0"
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
checksum: 10c0/bdb5b0bb44050ffa54425dbbea855e55696847ae8ff87f77fe43bc85d830d67735ecdeaaf188ba30d9567fad8b0c47a5f42a9a522ed9411222773512e0e7f37b
checksum: 10c0/89a3f775a9f2503a2ba4586e6080ddcb435331ef2fcf8cb3d2b29e69386fe21c387b94d8b7000d968357c354dcbf38f650bedeec319971d99c67249a6eec9137
languageName: node
linkType: hard

Expand Down