All files / packages/app/features/receipts receipt-preview-sync-icon.tsx

83.87% Statements 26/31
73.91% Branches 17/23
100% Functions 5/5
83.87% Lines 26/31

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                      30x     1477x     1623x   5x           1618x                                   30x     1741x 1741x 5x   1623x 9338x   1623x 15x   1608x 1561x 6930x             1561x 5x   1556x   47x 47x   47x         47x     47x 47x                     40x        
import type React from "react";
 
import { suspendedFallback } from "~app/components/suspense-wrapper";
import { useParticipantsWithDebts } from "~app/hooks/use-participants";
import type { Receipt } from "~app/trpc-types";
import { isDebtInSyncWithReceipt } from "~app/utils/debts";
import { Button } from "~components/button";
import { Icon } from "~components/icons";
import { Skeleton } from "~components/skeleton";
 
export const skeletonReceiptPreviewSyncIcon = (
	<Skeleton className="size-6 rounded-sm" />
);
 
const StatusButton: React.FC<{ type: "synced" | "desynced" | "unsynced" }> = ({
	type,
}) => {
	switch (type) {
		case "synced":
			return (
				<Button variant="light" isDisabled isIconOnly color="success">
					<Icon name="sync" className="size-6" />
				</Button>
			);
		case "unsynced":
			return (
				<Button variant="light" isDisabled isIconOnly color="warning">
					<Icon name="unsync" className="size-6" />
				</Button>
			);
		case "desynced":
			return (
				<Button variant="light" isDisabled isIconOnly color="danger">
					<Icon name="unsync" className="size-6" />
				</Button>
			);
	}
};
 
type Props = {
	receipt: Receipt;
};
 
export const ReceiptPreviewSyncIcon: React.FC<Props> = suspendedFallback(
	({ receipt }) => {
		const { participantsWithDebts, syncableParticipants } =
			useParticipantsWithDebts(receipt);
		if (receipt.items.length === 0 || participantsWithDebts.length === 0) {
			return null;
		}
		const hasNonDistributedItems = receipt.items.some(
			(item) => item.consumers.length === 0,
		);
		if (hasNonDistributedItems) {
			return <StatusButton type="unsynced" />;
		}
		if (receipt.selfPeerId === receipt.ownerPeerId) {
			const syncedParticipants = syncableParticipants.filter((participant) =>
				participant.currentDebt?.our
					? isDebtInSyncWithReceipt(
							{ ...receipt, participantSum: participant.balance },
							participant.currentDebt.our,
						)
					: false,
			);
			if (syncableParticipants.length === syncedParticipants.length) {
				return <StatusButton type="synced" />;
			}
			return <StatusButton type="unsynced" />;
		}
		const selfParticipant = participantsWithDebts.find(
			(participant) => participant.peerId === receipt.selfPeerId,
		);
		Iif (!selfParticipant) {
			throw new Error(
				`Expected to have yourself as self participant with foreign receipt.`,
			);
		}
		Iif (!syncableParticipants.includes(selfParticipant)) {
			return null;
		}
		Eif (!selfParticipant.currentDebt?.our) {
			return <StatusButton type="unsynced" />;
		}
 
		if (
			!isDebtInSyncWithReceipt(
				{ ...receipt, participantSum: -selfParticipant.balance },
				selfParticipant.currentDebt.our,
			)
		) {
			return <StatusButton type="desynced" />;
		}
		return <StatusButton type="synced" />;
	},
	skeletonReceiptPreviewSyncIcon,
);