turash/bugulma/frontend/components/resource-flow/ResourceFlowList.tsx
2025-12-15 10:06:41 +01:00

138 lines
4.6 KiB
TypeScript

import ResourceFlowCard from '@/components/resource-flow/ResourceFlowCard';
import Button from '@/components/ui/Button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { Flex, Stack } from '@/components/ui/layout';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs';
import { useResourceFlowsByOrganization } from '@/hooks/api';
import { useTranslation } from '@/hooks/useI18n';
import type { ResourceDirection } from '@/schemas/backend/resource-flow';
import { Plus } from 'lucide-react';
import React, { useMemo, useState } from 'react';
interface ResourceFlowListProps {
organizationId: string;
onAddResourceFlow?: (direction: ResourceDirection) => void;
onViewMatches?: (resourceId: string) => void;
}
const ResourceFlowList: React.FC<ResourceFlowListProps> = ({
organizationId,
onAddResourceFlow,
onViewMatches,
}) => {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState('inputs');
const { data: resourceFlows, isLoading, error } = useResourceFlowsByOrganization(organizationId);
// Normalize and memoize filtered flows to prevent recalculation on every render
const safeResourceFlows = useMemo(
() => (Array.isArray(resourceFlows) ? resourceFlows : []),
[resourceFlows]
);
const { inputFlows, outputFlows } = useMemo(() => {
return {
inputFlows: safeResourceFlows.filter((flow) => flow?.Direction === 'input'),
outputFlows: safeResourceFlows.filter((flow) => flow?.Direction === 'output'),
};
}, [safeResourceFlows]);
// Show loading state only if we don't have any placeholder data
if (isLoading && safeResourceFlows.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>{t('resourceFlows.title')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{t('common.loading')}</p>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card>
<CardHeader>
<CardTitle>{t('resourceFlows.title')}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-destructive">
{t('common.error')}: {error.message}
</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<Flex align="center" justify="between">
<CardTitle>{t('resourceFlows.title')}</CardTitle>
{onAddResourceFlow && (
<Flex gap="xs">
<Button variant="outline" size="sm" onClick={() => onAddResourceFlow('input')}>
<Plus className="h-4 w-4 mr-2" />
{t('resourceFlows.addInput')}
</Button>
<Button variant="outline" size="sm" onClick={() => onAddResourceFlow('output')}>
<Plus className="h-4 w-4 mr-2" />
{t('resourceFlows.addOutput')}
</Button>
</Flex>
)}
</Flex>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="inputs">
{t('resourceFlows.inputs')} ({inputFlows.length})
</TabsTrigger>
<TabsTrigger value="outputs">
{t('resourceFlows.outputs')} ({outputFlows.length})
</TabsTrigger>
</TabsList>
<TabsContent value="inputs" className="mt-4">
<Stack spacing="md">
{inputFlows.length > 0 ? (
inputFlows.map((flow) => (
<ResourceFlowCard
key={flow.ID}
resourceFlow={flow}
onViewMatches={onViewMatches}
/>
))
) : (
<p className="text-center text-muted-foreground py-8">
{t('resourceFlows.noInputs')}
</p>
)}
</Stack>
</TabsContent>
<TabsContent value="outputs" className="mt-4">
<Stack spacing="md">
{outputFlows.length > 0 ? (
outputFlows.map((flow) => (
<ResourceFlowCard
key={flow.ID}
resourceFlow={flow}
onViewMatches={onViewMatches}
/>
))
) : (
<p className="text-center text-muted-foreground py-8">
{t('resourceFlows.noOutputs')}
</p>
)}
</Stack>
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
};
export default React.memo(ResourceFlowList);