- Showing {(currentPage - 1) * pageSize + 1} to{' '}
- {Math.min(currentPage * pageSize, totalItems)} of {totalItems} results
+ {t('pagination.showing', {
+ start: (currentPage - 1) * pageSize + 1,
+ end: Math.min(currentPage * pageSize, totalItems),
+ total: totalItems,
+ })}
)}
diff --git a/bugulma/frontend/components/ui/Popover.tsx b/bugulma/frontend/components/ui/Popover.tsx
index 4932629..3e94b1f 100644
--- a/bugulma/frontend/components/ui/Popover.tsx
+++ b/bugulma/frontend/components/ui/Popover.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { clsx } from 'clsx';
export interface PopoverProps {
@@ -47,12 +47,15 @@ export const Popover = ({
const isControlled = controlledOpen !== undefined;
const open = isControlled ? controlledOpen : internalOpen;
- const setOpen = (value: boolean) => {
- if (!isControlled) {
- setInternalOpen(value);
- }
- onOpenChange?.(value);
- };
+ const setOpen = useCallback(
+ (value: boolean) => {
+ if (!isControlled) {
+ setInternalOpen(value);
+ }
+ onOpenChange?.(value);
+ },
+ [isControlled, onOpenChange]
+ );
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
@@ -81,7 +84,7 @@ export const Popover = ({
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscape);
};
- }, [open]);
+ }, [open, setOpen]);
return (
- {formatNumber(stats.co2_savings_tonnes)} t
+ {formatNumber(stats.co2_savings_tonnes)} {t('common.tonnes')}
{t('organizationDashboard.totalSavings')}
diff --git a/bugulma/frontend/pages/OrganizationEditPage.tsx b/bugulma/frontend/pages/OrganizationEditPage.tsx
index 10eb4d8..44a3d21 100644
--- a/bugulma/frontend/pages/OrganizationEditPage.tsx
+++ b/bugulma/frontend/pages/OrganizationEditPage.tsx
@@ -6,7 +6,7 @@ import PageHeader from '@/components/layout/PageHeader.tsx';
import Button from '@/components/ui/Button.tsx';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx';
import Input from '@/components/ui/Input.tsx';
-import { Label, FormField } from '@/components/ui';
+import { FormField } from '@/components/ui';
import { Container, Flex, Stack } from '@/components/ui/layout';
import Select from '@/components/ui/Select.tsx';
import Spinner from '@/components/ui/Spinner.tsx';
@@ -17,7 +17,6 @@ import { useCreateOrganization, useOrganization } from '@/hooks/api/useOrganizat
import { useTranslation } from '@/hooks/useI18n.tsx';
import { useNavigation } from '@/hooks/useNavigation.tsx';
import { isValidEmail, sanitizeInput, validateInput } from '@/lib/api-client.ts';
-import { getTranslatedSectorName } from '@/lib/sector-mapper.ts';
import { getOrganizationSubtypeLabel } from '@/schemas/organizationSubtype.ts';
const OrganizationEditPage = () => {
diff --git a/bugulma/frontend/pages/OrganizationsListPage.tsx b/bugulma/frontend/pages/OrganizationsListPage.tsx
index 368de39..e5760c1 100644
--- a/bugulma/frontend/pages/OrganizationsListPage.tsx
+++ b/bugulma/frontend/pages/OrganizationsListPage.tsx
@@ -16,6 +16,7 @@ import { useTranslation } from '@/hooks/useI18n.tsx';
import { useNavigation } from '@/hooks/useNavigation.tsx';
import { getTranslatedSectorName } from '@/lib/sector-mapper.ts';
import { getOrganizationSubtypeLabel } from '@/schemas/organizationSubtype.ts';
+import type { Organization } from '@/types.ts';
const OrganizationsListPage = () => {
const { t } = useTranslation();
@@ -36,7 +37,7 @@ const OrganizationsListPage = () => {
const processedOrganizations = useMemo(() => {
if (!organizations) return [];
- const filtered = organizations.filter((org: any) => {
+ const filtered = organizations.filter((org: Organization) => {
// Search filter
const matchesSearch =
!searchQuery ||
@@ -57,7 +58,7 @@ const OrganizationsListPage = () => {
});
// Sort
- filtered.sort((a: any, b: any) => {
+ filtered.sort((a: Organization, b: Organization) => {
switch (sortBy) {
case 'name':
return a.Name?.localeCompare(b.Name || '') || 0;
@@ -83,10 +84,10 @@ const OrganizationsListPage = () => {
if (!organizations) return { sectors: [], subtypes: [] };
const sectors = Array.from(
- new Set(organizations.map((org: any) => org.Sector).filter(Boolean))
+ new Set(organizations.map((org: Organization) => org.Sector).filter(Boolean))
);
const subtypes = Array.from(
- new Set(organizations.map((org: any) => org.Subtype).filter(Boolean))
+ new Set(organizations.map((org: Organization) => org.Subtype).filter(Boolean))
);
return { sectors, subtypes };
@@ -122,7 +123,7 @@ const OrganizationsListPage = () => {
navigate('/organizations/new');
};
- const handleOrganizationClick = (organization: any) => {
+ const handleOrganizationClick = (organization: Organization) => {
navigate(`/organization/${organization.ID}`);
};
@@ -305,7 +306,7 @@ const OrganizationsListPage = () => {
{/* Organizations Grid/List */}
{processedOrganizations.length > 0 ? (
- {processedOrganizations.map((organization: any) => (
+ {processedOrganizations.map((organization: Organization) => (
{
const { t } = useTranslation();
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
@@ -34,17 +45,20 @@ const SupplyDemandAnalysis = () => {
// Process supply/demand data
const analysis = useMemo(() => {
- const data = supplyDemandData || {};
+ const data: SupplyDemandAnalysis = supplyDemandData || {
+ top_needs: [],
+ top_offers: [],
+ };
- const topNeeds = (data as any).top_needs || [];
- const topOffers = (data as any).top_offers || [];
- const marketGaps = (data as any).market_gaps || [];
+ const topNeeds = data.top_needs || [];
+ const topOffers = data.top_offers || [];
+ const marketGaps: ItemCount[] = []; // TODO: Add market_gaps to schema if needed
// Create combined analysis
const resourceAnalysis = new Map();
// Process needs
- topNeeds.forEach((need: any) => {
+ topNeeds.forEach((need: ItemCount) => {
if (!resourceAnalysis.has(need.item)) {
resourceAnalysis.set(need.item, {
resource: need.item,
@@ -60,7 +74,7 @@ const SupplyDemandAnalysis = () => {
});
// Process offers
- topOffers.forEach((offer: any) => {
+ topOffers.forEach((offer: ItemCount) => {
if (!resourceAnalysis.has(offer.item)) {
resourceAnalysis.set(offer.item, {
resource: offer.item,
@@ -76,47 +90,53 @@ const SupplyDemandAnalysis = () => {
});
// Calculate gaps and status
- const analysisArray = Array.from(resourceAnalysis.values()).map((item: any) => {
- const gap = item.supply - item.demand;
- const total = item.supply + item.demand;
- const gapPercentage = total > 0 ? (gap / total) * 100 : 0;
+ const analysisArray = Array.from(resourceAnalysis.values()).map(
+ (item: ResourceAnalysisItem) => {
+ const gap = item.supply - item.demand;
+ const total = item.supply + item.demand;
+ const gapPercentage = total > 0 ? (gap / total) * 100 : 0;
- let status = 'balanced';
- if (gap > 10) status = 'surplus';
- else if (gap < -10) status = 'shortage';
+ let status = 'balanced';
+ if (gap > 10) status = 'surplus';
+ else if (gap < -10) status = 'shortage';
- return {
- ...item,
- gap,
- gapPercentage: Math.abs(gapPercentage),
- status,
- };
- });
+ return {
+ ...item,
+ gap,
+ gapPercentage: Math.abs(gapPercentage),
+ status,
+ };
+ }
+ );
// Filter by sector
const filteredAnalysis =
selectedSector === 'all'
? analysisArray
- : analysisArray.filter((item: any) => item.sector === selectedSector);
+ : analysisArray.filter((item: ResourceAnalysisItem) => item.sector === selectedSector);
// Sort
- const sortedAnalysis = filteredAnalysis.sort((a: any, b: any) => {
- switch (sortBy) {
- case 'gap':
- return Math.abs(b.gap) - Math.abs(a.gap);
- case 'demand':
- return b.demand - a.demand;
- case 'supply':
- return b.supply - a.supply;
- case 'resource':
- return a.resource.localeCompare(b.resource);
- default:
- return 0;
+ const sortedAnalysis = filteredAnalysis.sort(
+ (a: ResourceAnalysisItem, b: ResourceAnalysisItem) => {
+ switch (sortBy) {
+ case 'gap':
+ return Math.abs(b.gap) - Math.abs(a.gap);
+ case 'demand':
+ return b.demand - a.demand;
+ case 'supply':
+ return b.supply - a.supply;
+ case 'resource':
+ return a.resource.localeCompare(b.resource);
+ default:
+ return 0;
+ }
}
- });
+ );
// Get unique sectors
- const sectors = Array.from(new Set(analysisArray.map((item: any) => item.sector)));
+ const sectors = Array.from(
+ new Set(analysisArray.map((item: ResourceAnalysisItem) => item.sector))
+ );
return {
analysis: sortedAnalysis,
@@ -124,9 +144,15 @@ const SupplyDemandAnalysis = () => {
marketGaps,
summary: {
totalResources: analysisArray.length,
- surplusCount: analysisArray.filter((item: any) => item.status === 'surplus').length,
- shortageCount: analysisArray.filter((item: any) => item.status === 'shortage').length,
- balancedCount: analysisArray.filter((item: any) => item.status === 'balanced').length,
+ surplusCount: analysisArray.filter(
+ (item: ResourceAnalysisItem) => item.status === 'surplus'
+ ).length,
+ shortageCount: analysisArray.filter(
+ (item: ResourceAnalysisItem) => item.status === 'shortage'
+ ).length,
+ balancedCount: analysisArray.filter(
+ (item: ResourceAnalysisItem) => item.status === 'balanced'
+ ).length,
},
};
}, [supplyDemandData, selectedSector, sortBy]);
@@ -179,8 +205,12 @@ const SupplyDemandAnalysis = () => {
return (
- Supply: {supply}
- Demand: {demand}
+
+ {t('common.supply')}: {supply}
+
+
+ {t('common.demand')}: {demand}
+
{
) : analysis.analysis.length > 0 ? (
- {analysis.analysis.map((item: any, index: number) => (
+ {analysis.analysis.map((item: ResourceAnalysisItem, index: number) => (
{
- Gap: {item.gap > 0 ? '+' : ''}
+ {t('common.gap')}: {item.gap > 0 ? '+' : ''}
{item.gap}
- {item.gapPercentage.toFixed(1)}% imbalance
+ {item.gapPercentage.toFixed(1)}
+ {t('common.percent')} {t('common.imbalance')}
@@ -365,10 +396,14 @@ const SupplyDemandAnalysis = () => {
- Supply: {item.supply}
- Demand: {item.demand}
- Gap: {item.gap > 0 ? '+' : ''}
+ {t('common.supply')}: {item.supply}
+
+
+ {t('common.demand')}: {item.demand}
+
+
+ {t('common.gap')}: {item.gap > 0 ? '+' : ''}
{item.gap}
@@ -395,7 +430,7 @@ const SupplyDemandAnalysis = () => {
- {analysis.marketGaps.map((gap: any, index: number) => (
+ {analysis.marketGaps.map((gap: ItemCount, index: number) => (
diff --git a/bugulma/frontend/pages/UserDashboard.tsx b/bugulma/frontend/pages/UserDashboard.tsx
index bbfda41..c93afa9 100644
--- a/bugulma/frontend/pages/UserDashboard.tsx
+++ b/bugulma/frontend/pages/UserDashboard.tsx
@@ -14,7 +14,7 @@ import { useNavigation } from '@/hooks/useNavigation.tsx';
import type { BackendOrganization } from '@/schemas/backend/organization';
import type { Proposal } from '@/types.ts';
import { Target } from 'lucide-react';
-import { useCallback, useState } from 'react';
+import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
const UserDashboard = () => {
@@ -22,7 +22,6 @@ const UserDashboard = () => {
const { handleBackNavigation, handleFooterNavigate } = useNavigation();
const { user } = useAuth();
const navigate = useNavigate();
- const [selectedOrg, setSelectedOrg] = useState(null);
// Get all proposals for user's organizations
const { data: proposalsData, isLoading: isLoadingProposals } = useProposals();
diff --git a/bugulma/frontend/pages/admin/AdminDashboard.tsx b/bugulma/frontend/pages/admin/AdminDashboard.tsx
index 4ca2095..1bbff7a 100644
--- a/bugulma/frontend/pages/admin/AdminDashboard.tsx
+++ b/bugulma/frontend/pages/admin/AdminDashboard.tsx
@@ -50,8 +50,12 @@ const AdminDashboard = () => {
// Show error message if API call failed (likely 403 - not admin)
if (error && !isLoading) {
- const isForbidden = (error as any)?.status === 403;
- const errorData = (error as any)?.data;
+ const apiError = error as {
+ status?: number;
+ data?: { user_role?: string; required_role?: string };
+ };
+ const isForbidden = apiError?.status === 403;
+ const errorData = apiError?.data;
const userRole = errorData?.user_role || user?.role;
const requiredRole = errorData?.required_role || 'admin';
@@ -60,39 +64,37 @@ const AdminDashboard = () => {
{isForbidden && (
- Access Denied: You do not have administrator privileges to view this dashboard.
+ {t('common.accessDenied')}: {t('common.administratorPrivileges')}
- Your current role:{' '}
+ {t('common.currentRole')}{' '}
{userRole || 'unknown'}
- Required role:{' '}
+ {t('common.requiredRole')}{' '}
{requiredRole}
{userRole !== 'admin' && (
-
To fix this:
+
{t('common.fixThis')}
- - Your user account in the database needs to have role = 'admin'
- - You may need to log out and log back in after your role is updated
- - Contact your database administrator to update your role
+ - {t('common.contactAdmin')}
+ - {t('common.logoutAndLogin')}
+ - {t('common.contactAdmin')}
)}
-
- Please contact your administrator if you believe you should have access.
-
+
{t('common.contactAdminHelp')}
)}
{!isForbidden && (
- Error loading dashboard: {(error as Error)?.message || 'Unknown error'}
+ {t('common.errorLoadingDashboard')}: {(error as Error)?.message || 'Unknown error'}
)}
diff --git a/bugulma/frontend/pages/admin/AdminOrganizationEditPage.tsx b/bugulma/frontend/pages/admin/AdminOrganizationEditPage.tsx
index a11a36e..861e87c 100644
--- a/bugulma/frontend/pages/admin/AdminOrganizationEditPage.tsx
+++ b/bugulma/frontend/pages/admin/AdminOrganizationEditPage.tsx
@@ -8,7 +8,6 @@ import MapPicker from '@/components/ui/MapPicker';
import Select from '@/components/ui/Select';
import Textarea from '@/components/ui/Textarea';
import { useCreateOrganization, useUpdateOrganization } from '@/hooks/api/useOrganizationsAPI.ts';
-import { useTranslation } from '@/hooks/useI18n.tsx';
import { useOrganizations } from '@/hooks/useOrganizations.ts';
import { useToast } from '@/hooks/useToast.ts';
import { Organization } from '@/types.ts';
@@ -50,7 +49,6 @@ const organizationSchema = z.object({
type OrganizationFormData = z.infer;
const AdminOrganizationEditPage = () => {
- const { t } = useTranslation();
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
const isEditing = !!id;
@@ -205,22 +203,20 @@ const AdminOrganizationEditPage = () => {
- {isEditing ? 'Edit Organization' : 'Create New Organization'}
+ {isEditing ? t('admin.editOrganization') : t('admin.createOrganization')}
- {isEditing
- ? 'Update organization details'
- : 'Add a new organization to the ecosystem'}
+ {isEditing ? t('admin.updateOrganizationDetails') : t('admin.addNewOrganization')}