turash/bugulma/frontend/components/organization/ProposalList.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

105 lines
3.6 KiB
TypeScript

import React, { useCallback, useMemo, useState } from 'react';
import {
useProposalsForOrganization,
useUpdateProposalStatus,
} from '@/hooks/api/useProposalsAPI.ts';
import { useTranslation } from '@/hooks/useI18n.tsx';
import { useOrganizations } from '@/hooks/useOrganizations.ts';
import { Organization } from '@/types.ts';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs.tsx';
import ProposalCard from '@/components/organization/ProposalCard.tsx';
interface ProposalListProps {
organization: Organization;
}
const ProposalList = ({ organization }: ProposalListProps) => {
const { t } = useTranslation();
const { organizations } = useOrganizations();
const { data: proposalsData, isLoading } = useProposalsForOrganization(organization.ID);
const updateProposalStatus = useUpdateProposalStatus();
const [activeTab, setActiveTab] = useState('incoming');
// Memoize proposals arrays to prevent unnecessary recalculations
const incoming = useMemo(
() => (Array.isArray(proposalsData?.incoming) ? proposalsData.incoming : []),
[proposalsData]
);
const outgoing = useMemo(
() => (Array.isArray(proposalsData?.outgoing) ? proposalsData.outgoing : []),
[proposalsData]
);
// Memoize findOrg function to prevent recreation
const findOrg = useCallback(
(id: string) => {
if (!id || !organizations || !Array.isArray(organizations)) return undefined;
return organizations.find((o) => o?.ID === id);
},
[organizations]
);
// Memoize status update handler
const handleStatusUpdate = useCallback(
(id: string, status: 'pending' | 'accepted' | 'rejected') => {
updateProposalStatus.mutate({ id, status });
},
[updateProposalStatus]
);
const renderProposalList = useCallback(
(proposals: typeof incoming, type: 'incoming' | 'outgoing') => {
if (isLoading) {
return <p className="text-center text-muted-foreground py-8">{t('common.loading')}</p>;
}
if (!Array.isArray(proposals) || proposals.length === 0) {
return (
<p className="text-center text-muted-foreground py-8">
{t('organizationPage.partnershipHub.noProposals')}
</p>
);
}
return (
<div className="space-y-3 max-h-96 overflow-y-auto pr-2">
{proposals
.filter((p) => p?.id) // Filter out invalid proposals
.map((p) => {
const partnerOrg = type === 'incoming' ? findOrg(p.fromOrgId) : findOrg(p.toOrgId);
if (!partnerOrg) return null;
return (
<ProposalCard
key={p.id}
proposal={p}
partner={partnerOrg}
type={type}
onStatusUpdate={handleStatusUpdate}
/>
);
})
.filter(Boolean)}{' '}
{/* Remove null entries */}
</div>
);
},
[isLoading, t, findOrg, handleStatusUpdate]
);
return (
<Tabs value={activeTab} onValueChange={setActiveTab} className="mt-2">
<TabsList className="w-full grid grid-cols-2">
<TabsTrigger value="incoming">
{t('organizationPage.partnershipHub.incoming')} ({incoming.length})
</TabsTrigger>
<TabsTrigger value="outgoing">
{t('organizationPage.partnershipHub.outgoing')} ({outgoing.length})
</TabsTrigger>
</TabsList>
<TabsContent value="incoming">{renderProposalList(incoming, 'incoming')}</TabsContent>
<TabsContent value="outgoing">{renderProposalList(outgoing, 'outgoing')}</TabsContent>
</Tabs>
);
};
export default React.memo(ProposalList);