Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
auto-install-peers = false
//npm.pkg.github.com/:_authToken=${GH_PAT}
@open-source-bazaar:registry=https://npm.pkg.github.com
Comment thread
TechQuery marked this conversation as resolved.
67 changes: 67 additions & 0 deletions components/Map/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { FC, useEffect, useRef } from 'react';
import { Card, Col, Container, Row, Spinner } from 'react-bootstrap';

export interface MapProps {
data?: { name: string; city?: string; province?: string; latitude?: number; longitude?: number }[];
loading?: boolean;
}

export const Map: FC<MapProps> = ({ data = [], loading = false }) => {
const mapRef = useRef<HTMLDivElement>(null);

useEffect(() => {
// Placeholder for actual map implementation
// This would integrate with a mapping library like Leaflet, AMap, or MapBox
}, []);
Comment thread
TechQuery marked this conversation as resolved.
Outdated

if (loading) {
return (
<Container className="d-flex justify-content-center align-items-center" style={{ minHeight: '400px' }}>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</Container>
);
}

return (
<Container>
<Row>
<Col>
<Card>
<Card.Body>
<div
ref={mapRef}
style={{
width: '100%',
height: '500px',
backgroundColor: '#f8f9fa',
border: '1px solid #dee2e6',
borderRadius: '0.375rem',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div className="text-center text-muted">
<h5>Interactive Map Coming Soon</h5>
<p>Geographic visualization of {data.length} organizations</p>
{data.length > 0 && (
<small>
Data includes organizations from{' '}
{[...new Set(data.map(item => item.province || item.city).filter(Boolean))].length}{' '}
locations
</small>
)}
</div>
</div>
</Card.Body>
</Card>
</Col>
</Row>
</Container>
);
};

export default Map;
10 changes: 7 additions & 3 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
],
},
{
title: t('china_public_interest_map'),
title: t('ngo'),
subs: [
{ href: '/organization', title: t('china_public_interest_map') },
{ href: '/organization/landscape', title: t('china_public_interest_landscape') },
{ href: '/ngo', title: t('china_public_interest_map') },
{ href: '/ngo/landscape', title: t('china_public_interest_landscape') },
{
href: 'https://open-source-bazaar.feishu.cn/wiki/VGrMwiweVivWrHkTcvpcJTjjnoY',
title: t('open_source_public_interest_plan')
},
],
},
];
Expand Down
178 changes: 107 additions & 71 deletions components/Organization/ChinaPublicInterestLandscape.tsx
Comment thread
TechQuery marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -1,81 +1,117 @@
import { observer } from 'mobx-react';
import { FC, useContext } from 'react';
import { Badge,Card, Col, Row } from 'react-bootstrap';
import { FC, useContext, useEffect, useState } from 'react';
import { Badge, Card, Col, Container, Row } from 'react-bootstrap';

import { Organization } from '../../models/Organization';
import { Organization, OrganizationModel } from '../../models/Organization';
import { I18nContext } from '../../models/Translation';

export interface ChinaPublicInterestLandscapeProps {
tagMap: Record<string, Organization[]>;
}
const organizationModel = new OrganizationModel();

export const ChinaPublicInterestLandscape: FC<ChinaPublicInterestLandscapeProps> = observer(
({ tagMap }) => {
const { t } = useContext(I18nContext);
export const ChinaPublicInterestLandscape: FC = observer(() => {
const { t } = useContext(I18nContext);
const [tagMap, setTagMap] = useState<Record<string, Organization[]>>({});
const [loading, setLoading] = useState(false);

const tagEntries = Object.entries(tagMap).sort(([, a], [, b]) => b.length - a.length);
useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
const groupedData = await organizationModel.groupAllByTags();
setTagMap(groupedData);
} finally {
setLoading(false);
}
};

return (
<div>
{tagEntries.length === 0 && (
<Card>
<Card.Body className="text-center py-5">
<Card.Title>{t('no_data_available')}</Card.Title>
<Card.Text>{t('landscape_data_loading_message')}</Card.Text>
</Card.Body>
</Card>
)}
loadData();
}, []);

{tagEntries.map(([tag, organizations]) => (
<Card key={tag} className="mb-4">
<Card.Header className="d-flex justify-content-between align-items-center">
<h5 className="mb-0">{tag}</h5>
<Badge bg="primary">{organizations.length} {t('organizations')}</Badge>
</Card.Header>
<Card.Body>
<Row className="g-3">
{organizations.map(org => (
<Col key={org.id} md={6} lg={4}>
<Card className="h-100">
<Card.Body>
<Card.Title className="h6">{org.name}</Card.Title>
{org.description && (
<Card.Text className="small text-muted">
{org.description.length > 100
? `${org.description.substring(0, 100)}...`
: org.description}
</Card.Text>
)}
<div className="mt-2">
{org.city && (
<Badge bg="secondary" className="me-1">
{org.city}
</Badge>
const tagEntries = Object.entries(tagMap).sort(([, a], [, b]) => b.length - a.length);

return (
<Container className="py-4">
<Row className="mb-4">
<Col>
<h1>{t('china_public_interest_landscape')}</h1>
</Col>
</Row>

{loading && (
<Row>
<Col className="text-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</Col>
</Row>
)}

{!loading && tagEntries.length === 0 && (
<Row>
<Col>
<Card>
<Card.Body className="text-center py-5">
<Card.Title>{t('no_data_available')}</Card.Title>
<Card.Text>{t('landscape_data_loading_message')}</Card.Text>
</Card.Body>
</Card>
</Col>
</Row>
)}

{!loading && tagEntries.map(([tag, organizations]) => (
<Row key={tag} className="mb-4">
<Col>
<Card>
<Card.Header className="d-flex justify-content-between align-items-center">
<h5 className="mb-0">{tag}</h5>
<Badge bg="primary">{organizations.length} {t('organizations')}</Badge>
</Card.Header>
<Card.Body>
<Row className="g-3">
{organizations.map(org => (
<Col key={org.id} md={6} lg={4}>
<Card className="h-100">
<Card.Body>
<Card.Title className="h6">{org.name}</Card.Title>
{org.description && (
<Card.Text className="small text-muted">
{org.description.length > 100
? `${org.description.substring(0, 100)}...`
: org.description}
</Card.Text>
)}
{org.type && (
<Badge bg="info" className="me-1">
{org.type}
</Badge>
<div className="mt-2">
{org.city && (
<Badge bg="secondary" className="me-1">
{org.city}
</Badge>
)}
{org.type && (
<Badge bg="info" className="me-1">
{org.type}
</Badge>
)}
</div>
{org.website && (
<Card.Link
href={org.website}
target="_blank"
className="small"
>
{t('visit_website')}
</Card.Link>
)}
</div>
{org.website && (
<Card.Link
href={org.website}
target="_blank"
className="small"
>
{t('visit_website')}
</Card.Link>
)}
</Card.Body>
</Card>
</Col>
))}
</Row>
</Card.Body>
</Card>
))}
</div>
);
},
);
</Card.Body>
</Card>
</Col>
))}
</Row>
</Card.Body>
</Card>
</Col>
</Row>
))}
</Container>
);
});
Loading