All files / packages/app/features/receipt-components receipt-items.tsx

100% Statements 27/27
100% Branches 4/4
100% Functions 11/11
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                                  88x 29x 29x                           88x 558x 558x 558x   558x 45x   513x                                       88x 601x 601x 601x   601x 1198x       601x 129x             419x         897x       359x               6x     147x 441x     147x     441x          
import React from "react";
 
import { Trans, useTranslation } from "react-i18next";
 
import { useBooleanState } from "~app/hooks/use-boolean-state";
import { Button } from "~components/button";
import { Icon } from "~components/icons";
import { Text } from "~components/text";
import { View } from "~components/view";
import type { ViewHandle } from "~components/view.base";
import type { ReceiptItemId } from "~db/ids";
 
import { AddReceiptItemForm } from "./add-receipt-item-form";
import { useReceiptContext } from "./context";
import { ReceiptEmptyItems } from "./receipt-empty-items";
import { ReceiptItem, ReceiptItemSkeleton } from "./receipt-item";
 
export const SkeletonAddReceiptItemController = () => {
	const { t } = useTranslation("receipts");
	return (
		<Button color="primary" variant="bordered" className="w-full" isDisabled>
			<Trans
				t={t}
				i18nKey="add.addItemButton"
				components={{
					icon: <Icon name="add" className="size-6" />,
					text: <Text />,
				}}
			/>
		</Button>
	);
};
 
const AddReceiptItemController: React.FC = () => {
	const { t } = useTranslation("receipts");
	const { receiptDisabled } = useReceiptContext();
	const [isOpen, { setTrue: openForm }] = useBooleanState();
 
	if (isOpen) {
		return <AddReceiptItemForm />;
	}
	return (
		<Button
			color="primary"
			variant="bordered"
			onPress={openForm}
			className="w-full"
			isDisabled={receiptDisabled}
		>
			<Trans
				t={t}
				i18nKey="add.addItemButton"
				components={{
					icon: <Icon name="add" className="size-6" />,
					text: <Text />,
				}}
			/>
		</Button>
	);
};
 
export const ReceiptItems: React.FC = () => {
	const { items, emptyReceiptElement } = useReceiptContext();
	const itemsRef = React.useRef<Record<ReceiptItemId, ViewHandle | null>>({});
	const sortedItems = React.useMemo(
		() =>
			items.toSorted((a, b) =>
				Temporal.ZonedDateTime.compare(a.createdAt, b.createdAt),
			),
		[items],
	);
	if (items.length === 0) {
		return (
			<View className="gap-4" testID="receipt-items">
				<AddReceiptItemController />
				<>{emptyReceiptElement}</>
			</View>
		);
	}
	return (
		<View className="gap-4" testID="receipt-items">
			<AddReceiptItemController />
			<ReceiptEmptyItems itemsRef={itemsRef} />
			{sortedItems.map((item) => (
				<ReceiptItem
					key={item.id}
					item={item}
					ref={(element) => {
						itemsRef.current[item.id] = element;
					}}
				/>
			))}
		</View>
	);
};
 
export const ReceiptItemsSkeleton: React.FC<{ amount: number }> = ({
	amount,
}) => {
	const items = React.useMemo(
		() => Array.from({ length: amount }, (_, index) => index),
		[amount],
	);
	return (
		<>
			{items.map((index) => (
				<ReceiptItemSkeleton key={index} />
			))}
		</>
	);
};