turash/bugulma/frontend/pages/ResourceFlowDetailPage.tsx
2025-12-15 10:06:41 +01:00

293 lines
9.9 KiB
TypeScript

import { MainLayout } from '@/components/layout/MainLayout.tsx';
import PageHeader from '@/components/layout/PageHeader.tsx';
import MatchesList from '@/components/matches/MatchesList.tsx';
import ResourceFlowCard from '@/components/resource-flow/ResourceFlowCard.tsx';
import { formatQuantity } from '@/components/resource-flow/utils';
import Badge from '@/components/ui/Badge.tsx';
import Button from '@/components/ui/Button.tsx';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx';
import { Container, Flex, Grid, Stack } from '@/components/ui/layout';
import { useResourceFlow } from '@/hooks/api/useResourcesAPI.ts';
import { useTranslation } from '@/hooks/useI18n.tsx';
import { useNavigation } from '@/hooks/useNavigation.tsx';
import type { BackendResourceFlow } from '@/schemas/backend/resource-flow';
import { DollarSign, FlaskConical, Thermometer, TrendingUp } from 'lucide-react';
import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
const ResourceFlowDetailPage = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { t } = useTranslation();
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
const { data: resourceFlow, isLoading, error } = useResourceFlow(id);
if (isLoading) {
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<div className="animate-pulse">
<div className="h-8 bg-muted rounded w-1/3 mb-8"></div>
<div className="h-64 bg-muted rounded mb-8"></div>
<div className="h-48 bg-muted rounded"></div>
</div>
</Container>
</MainLayout>
);
}
if (error || !resourceFlow) {
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<PageHeader
title={t('resourceFlowDetail.errorTitle')}
subtitle={t('resourceFlowDetail.errorSubtitle')}
onBack={handleBackNavigation}
/>
<Card>
<CardContent className="py-12">
<div className="text-center">
<p className="text-destructive mb-4">
{error?.message || t('resourceFlowDetail.notFound')}
</p>
<Button onClick={handleBackNavigation}>{t('common.back')}</Button>
</div>
</CardContent>
</Card>
</Container>
</MainLayout>
);
}
const handleViewMatches = (resourceId: string) => {
navigate(`/matching?resourceId=${resourceId}`);
};
return (
<MainLayout onNavigate={handleFooterNavigate} className="bg-muted/30">
<Container size="2xl" className="py-8 sm:py-12">
<PageHeader
title={t('resourceFlowDetail.title')}
subtitle={`${resourceFlow.Type} ${resourceFlow.Direction}`}
onBack={handleBackNavigation}
/>
<Stack spacing="2xl">
{/* Resource Flow Overview */}
<ResourceFlowCard
resourceFlow={resourceFlow}
onViewMatches={handleViewMatches}
showFullDetails={true}
/>
{/* Key Metrics */}
<Grid cols={{ md: 2, lg: 4 }} gap="md">
<ResourceFlowMetrics resourceFlow={resourceFlow} />
</Grid>
{/* Quality Parameters */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FlaskConical className="h-4 h-5 text-current w-4 w-5" />
{t('resourceFlowDetail.qualityParameters')}
</CardTitle>
</CardHeader>
<CardContent>
<QualityParametersTable resourceFlow={resourceFlow} />
</CardContent>
</Card>
{/* Economic Data */}
{(resourceFlow.EconomicData?.cost_in ||
resourceFlow.EconomicData?.cost_out ||
resourceFlow.EconomicData?.waste_disposal_cost) && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<DollarSign className="h-4 h-5 text-current w-4 w-5" />
{t('resourceFlowDetail.economicData')}
</CardTitle>
</CardHeader>
<CardContent>
<EconomicDataDisplay resourceFlow={resourceFlow} />
</CardContent>
</Card>
)}
{/* Related Matches */}
<MatchesList resourceId={resourceFlow.ID} maxDistanceKm={50} minScore={0.5} limit={10} />
</Stack>
</Container>
</MainLayout>
);
};
// Component for displaying key metrics
const ResourceFlowMetrics: React.FC<{ resourceFlow: BackendResourceFlow }> = ({ resourceFlow }) => {
const { t } = useTranslation();
const metrics = [
{
label: t('resourceFlowDetail.direction'),
value: (
<Badge variant={resourceFlow.Direction === 'input' ? 'secondary' : 'default'}>
{t(`resourceFlowDirection.${resourceFlow.Direction}`)}
</Badge>
),
icon: <TrendingUp className="h-4 h-5 text-current text-primary w-4 w-5" />,
},
{
label: t('resourceFlowDetail.type'),
value: <Badge variant="outline">{t(`resourceTypes.${resourceFlow.Type}`)}</Badge>,
icon: <FlaskConical className="h-4 h-5 text-current text-primary w-4 w-5" />,
},
{
label: t('resourceFlowDetail.precision'),
value: (
<Badge variant="outline">
{t(`precisionLevels.${resourceFlow.PrecisionLevel || 'estimated'}`)}
</Badge>
),
icon: <Thermometer className="h-4 h-5 text-current text-primary w-4 w-5" />,
},
{
label: t('resourceFlowDetail.source'),
value: (
<Badge variant="outline">{t(`sourceTypes.${resourceFlow.SourceType || 'declared'}`)}</Badge>
),
icon: <DollarSign className="h-4 h-5 text-current text-primary w-4 w-5" />,
},
];
return (
<>
{metrics.map((metric, index) => (
<Card key={index}>
<CardContent className="p-4">
<Flex align="center" gap="sm" className="mb-2">
{metric.icon}
<span className="text-sm font-medium text-muted-foreground">{metric.label}</span>
</Flex>
<div className="text-lg font-semibold">{metric.value}</div>
</CardContent>
</Card>
))}
</>
);
};
// Component for displaying quality parameters
const QualityParametersTable: React.FC<{ resourceFlow: BackendResourceFlow }> = ({
resourceFlow,
}) => {
const { t } = useTranslation();
const qualityData = resourceFlow.Quality || {};
const quantityData = resourceFlow.Quantity || {};
const parameters = [
{
label: t('resourceFlowDetail.quantity.amount'),
value: formatQuantity(quantityData) || t('common.na'),
},
{
label: t('resourceFlowDetail.quantity.temporalUnit'),
value: quantityData.temporal_unit
? t(`temporalUnits.${quantityData.temporal_unit}`)
: t('common.na'),
},
{
label: t('resourceFlowDetail.quality.temperature'),
value:
qualityData.temperature_celsius !== undefined
? `${qualityData.temperature_celsius}°C`
: t('common.na'),
},
{
label: t('resourceFlowDetail.quality.pressure'),
value:
qualityData.pressure_bar !== undefined ? `${qualityData.pressure_bar} bar` : t('common.na'),
},
{
label: t('resourceFlowDetail.quality.purity'),
value: qualityData.purity_pct ? `${qualityData.purity_pct}%` : t('common.na'),
},
{
label: t('resourceFlowDetail.quality.physicalState'),
value: qualityData.physical_state
? t(`physicalStates.${qualityData.physical_state}`)
: t('common.na'),
},
];
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{parameters.map((param, index) => (
<div
key={index}
className="flex justify-between items-center py-2 border-b border-muted last:border-b-0"
>
<span className="text-sm font-medium">{param.label}</span>
<span className="text-sm text-muted-foreground">{param.value}</span>
</div>
))}
</div>
);
};
// Component for displaying economic data
const EconomicDataDisplay: React.FC<{ resourceFlow: BackendResourceFlow }> = ({ resourceFlow }) => {
const { t } = useTranslation();
const economicData = resourceFlow.EconomicData || {};
const costs = [
{
label: t('resourceFlowDetail.economic.costIn'),
value: economicData.cost_in
? `${economicData.cost_in}/${resourceFlow.Quantity?.unit || 'unit'}`
: null,
},
{
label: t('resourceFlowDetail.economic.costOut'),
value: economicData.cost_out
? `${economicData.cost_out}/${resourceFlow.Quantity?.unit || 'unit'}`
: null,
},
{
label: t('resourceFlowDetail.economic.wasteCost'),
value: economicData.waste_disposal_cost
? `${economicData.waste_disposal_cost}/${resourceFlow.Quantity?.unit || 'unit'}`
: null,
},
{
label: t('resourceFlowDetail.economic.transportCost'),
value: economicData.transportation_cost ? `${economicData.transportation_cost}/km` : null,
},
].filter((cost) => cost.value);
if (costs.length === 0) {
return (
<p className="text-muted-foreground text-center py-4">
{t('resourceFlowDetail.economic.noData')}
</p>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{costs.map((cost, index) => (
<div key={index} className="flex justify-between items-center py-2">
<span className="text-sm font-medium">{cost.label}</span>
<span className="text-sm font-semibold text-primary">{cost.value}</span>
</div>
))}
</div>
);
};
export default ResourceFlowDetailPage;