All files / packages/app/features/debts-intentions debts-intentions.tsx

73.52% Statements 25/34
60% Branches 6/10
80% Functions 12/15
73.52% Lines 25/34

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                                                6x 48x       120x         13x         13x   49x 49x 49x     49x 42x     53x 53x 53x   42x   31x 31x                                   49x 12x   43x       39x         37x     64x               24x 24x         48x            
import React from "react";
 
import { useSuspenseQuery } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { entries, mapValues } from "remeda";
 
import { LoadablePeer } from "~app/components/app/loadable-peer";
import { SkeletonPeer } from "~app/components/app/peer";
import { EmptyCard } from "~app/components/empty-card";
import { suspendedFallback } from "~app/components/suspense-wrapper";
import type { TRPCQuerySuccessResult } from "~app/trpc";
import { useTRPC } from "~app/utils/trpc";
import { Button } from "~components/button";
import { View } from "~components/view";
import type { PeerId } from "~db/ids";
 
import { AcceptAllIntentionsButton } from "./accept-all-intentions-button";
import {
	InboundDebtIntention,
	SkeletonInboundDebtIntention,
} from "./inbound-debt-intention";
 
type IntentionsQuery = TRPCQuerySuccessResult<"debtIntentions.getAll">;
 
const AggregatedIntentionGroup: React.FC<{ amount: number }> = ({ amount }) => (
	<View className="gap-4">
		<SkeletonPeer className="self-start" />
		{Array.from({ length: amount }).map((_, index) => (
			// oxlint-disable-next-line react/no-array-index-key
			<SkeletonInboundDebtIntention key={index} />
		))}
	</View>
);
 
const getLatestIntention = (intentions: IntentionsQuery["data"]["items"]) =>
	intentions.toSorted((intentionA, intentionB) =>
		Temporal.PlainDate.compare(intentionB.timestamp, intentionA.timestamp),
	)[0];
 
export const DebtIntentions: React.FC = suspendedFallback(
	() => {
		const { t } = useTranslation("debts");
		const trpc = useTRPC();
		const { data: intentions } = useSuspenseQuery(
			trpc.debtIntentions.getAll.queryOptions(),
		);
		const aggregatedIntentions = React.useMemo(() => {
			const intentionsByPeer = intentions.items.reduce<
				Record<PeerId, IntentionsQuery["data"]["items"]>
			>((acc, intention) => {
				const peerIntentions = acc[intention.peerId] || [];
				peerIntentions.push(intention);
				return { ...acc, [intention.peerId]: peerIntentions };
			}, {});
			return entries(
				mapValues(intentionsByPeer, (peerIntentions) =>
					peerIntentions.toSorted((intentionA, intentionB) =>
						Temporal.PlainDate.compare(
							intentionA.timestamp,
							intentionB.timestamp,
						),
					),
				),
			).toSorted(([, groupedIntentionsA], [, groupedIntentionsB]) => {
				const latestA = getLatestIntention(groupedIntentionsA);
				const latestB = getLatestIntention(groupedIntentionsB);
				if (!latestA) {
					return -1;
				}
				if (!latestB) {
					return 1;
				}
				return Temporal.PlainDate.compare(latestA.timestamp, latestB.timestamp);
			});
		}, [intentions]);
		if (intentions.items.length === 0) {
			return <EmptyCard title={t("intentions.empty.title")} />;
		}
		return (
			<View className="flex gap-8">
				{intentions.items.length === 1 ? null : (
					<AcceptAllIntentionsButton
						key={intentions.items.map(({ id }) => id).length}
						intentions={intentions.items}
					/>
				)}
				{aggregatedIntentions.map(([peerId, peerIntentions]) => (
					<View className="gap-4" key={peerId}>
						<LoadablePeer className="self-start" id={peerId} />
						{peerIntentions.map((intention) => (
							<InboundDebtIntention key={intention.id} intention={intention} />
						))}
					</View>
				))}
			</View>
		);
	},
	() => {
		const { t } = useTranslation("debts");
		return (
			<View className="flex gap-8">
				<Button color="primary">{t("intentions.acceptAllButton")}</Button>
				{Array.from({ length: 2 }).map((_, index) => (
					// oxlint-disable-next-line react/no-array-index-key
					<AggregatedIntentionGroup key={index} amount={index + 2} />
				))}
			</View>
		);
	},
);