Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
auto-install-peers = false
auto-install-peers = false
@open-source-bazaar:registry=https://npm.pkg.github.com
21 changes: 6 additions & 15 deletions components/LarkImage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { TableCellValue } from 'mobx-lark';
import { FC } from 'react';
import { Image, ImageProps } from 'react-bootstrap';

import { fileURLOf } from '../models/Base';
import { DefaultImage } from '../models/configuration';

export interface LarkImageProps extends Omit<ImageProps, 'src'> {
src?: TableCellValue;
export interface SimpleImageProps extends ImageProps {
src?: string;
}

export const LarkImage: FC<LarkImageProps> = ({
export const LarkImage: FC<SimpleImageProps> = ({
src = DefaultImage,
alt,
...props
Expand All @@ -18,18 +16,11 @@ export const LarkImage: FC<LarkImageProps> = ({
fluid
loading="lazy"
{...props}
src={fileURLOf(src, true)}
src={src || DefaultImage}
alt={alt}
onError={({ currentTarget: image }) => {
const path = fileURLOf(src),
errorURL = decodeURI(image.src);

if (!path) return;

if (errorURL.endsWith(path)) {
if (!alt) image.src = DefaultImage;
} else if (!errorURL.endsWith(DefaultImage)) {
image.src = path;
if (image.src !== DefaultImage) {
image.src = DefaultImage;
}
}}
/>
Expand Down
7 changes: 7 additions & 0 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
{ href: '/policy', title: t('policy') },
],
},
{
title: t('china_public_interest_map'),
subs: [
{ href: '/organization', title: t('china_public_interest_map') },
{ href: '/organization/landscape', title: t('china_public_interest_landscape') },
],
},
];

export interface MainNavigatorProps {
Expand Down
81 changes: 81 additions & 0 deletions components/Organization/ChinaPublicInterestLandscape.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { observer } from 'mobx-react';
import { FC, useContext } from 'react';
import { Badge,Card, Col, Row } from 'react-bootstrap';

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

export interface ChinaPublicInterestLandscapeProps {
tagMap: Record<string, Organization[]>;
}

export const ChinaPublicInterestLandscape: FC<ChinaPublicInterestLandscapeProps> = observer(
({ tagMap }) => {
const { t } = useContext(I18nContext);

const tagEntries = Object.entries(tagMap).sort(([, a], [, b]) => b.length - a.length);

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>
)}

{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>
)}
{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>
)}
</Card.Body>
</Card>
</Col>
))}
</Row>
</Card.Body>
</Card>
))}
</div>
);
},
);
91 changes: 91 additions & 0 deletions components/Organization/ChinaPublicInterestMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { observer } from 'mobx-react';
import { FC, useContext } from 'react';
import { Badge,Card, Col, Row } from 'react-bootstrap';

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

export interface ChinaPublicInterestMapProps extends OrganizationStatistic {
store: OrganizationModel;
}

export const ChinaPublicInterestMap: FC<ChinaPublicInterestMapProps> = observer(
({ store, year, city, type, tag }) => {
const { t } = useContext(I18nContext);

return (
<div>
<Row className="g-4">
<Col md={6} lg={3}>
<Card>
<Card.Body>
<Card.Title>{t('by_year')}</Card.Title>
<div>
{year.slice(0, 5).map(item => (
<Badge key={item.label} bg="primary" className="me-2 mb-2">
{item.label}: {item.count}
</Badge>
))}
</div>
</Card.Body>
</Card>
</Col>

<Col md={6} lg={3}>
<Card>
<Card.Body>
<Card.Title>{t('by_city')}</Card.Title>
<div>
{city.slice(0, 5).map(item => (
<Badge key={item.label} bg="success" className="me-2 mb-2">
{item.label}: {item.count}
</Badge>
))}
</div>
</Card.Body>
</Card>
</Col>

<Col md={6} lg={3}>
<Card>
<Card.Body>
<Card.Title>{t('by_type')}</Card.Title>
<div>
{type.slice(0, 5).map(item => (
<Badge key={item.label} bg="info" className="me-2 mb-2">
{item.label}: {item.count}
</Badge>
))}
</div>
</Card.Body>
</Card>
</Col>

<Col md={6} lg={3}>
<Card>
<Card.Body>
<Card.Title>{t('by_tag')}</Card.Title>
<div>
{tag.slice(0, 5).map(item => (
<Badge key={item.label} bg="warning" className="me-2 mb-2">
{item.label}: {item.count}
</Badge>
))}
</div>
</Card.Body>
</Card>
</Col>
</Row>

<Card className="mt-4">
<Card.Body>
<Card.Title>{t('about_china_public_interest_map')}</Card.Title>
<Card.Text>
{t('china_public_interest_map_description')}
</Card.Text>
</Card.Body>
</Card>
</div>
);
},
);
19 changes: 3 additions & 16 deletions models/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'core-js/full/array/from-async';

import { HTTPClient } from 'koajax';
import { githubClient } from 'mobx-github';
import { TableCellAttachment, TableCellMedia, TableCellValue } from 'mobx-lark';
import { DataObject } from 'mobx-restful';
import { isEmpty } from 'web-utility';

Expand All @@ -11,7 +10,6 @@ import {
GithubToken,
isServer,
ProxyBaseURL,
LARK_API_HOST,
} from './configuration';

export const ownClient = new HTTPClient({
Expand Down Expand Up @@ -49,19 +47,8 @@ export const makeGithubSearchCondition = (queryMap: DataObject) =>
.map(([key, value]) => `${key}:${value}`)
.join(' ');

export const larkClient = new HTTPClient({
baseURI: LARK_API_HOST,
// Strapi client for China NGO Database
export const strapiClient = new HTTPClient({
baseURI: 'https://china-ngo-db.onrender.com/api/',
responseType: 'json',
});

export function fileURLOf(field: TableCellValue, cache = false) {
if (!(field instanceof Array) || !field[0]) return field + '';

const file = field[0] as TableCellMedia | TableCellAttachment;

let URI = `/api/Lark/file/${'file_token' in file ? file.file_token : file.attachmentToken}/${file.name}`;

if (cache) URI += '?cache=1';

return URI;
}
99 changes: 99 additions & 0 deletions models/Organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { observable } from 'mobx';
import { HTTPClient } from 'koajax';
import { StrapiListModel, Base } from 'mobx-strapi';

// Define the organization data structure similar to China NGO database
export interface Organization extends Base {
name: string;
description?: string;
type?: string;
city?: string;
province?: string;
tags?: string[];
website?: string;
logo?: {
data?: {
attributes: {
url: string;
};
};
};
year?: number;
}

export interface OrganizationStatistic {
year: Array<{ label: string; count: number }>;
city: Array<{ label: string; count: number }>;
type: Array<{ label: string; count: number }>;
tag: Array<{ label: string; count: number }>;
}

// Strapi client configuration
const strapiClient = new HTTPClient({
baseURI: 'https://china-ngo-db.onrender.com/api/',
responseType: 'json',
});

export class OrganizationModel extends StrapiListModel<Organization> {
baseURI = '/organizations';
client = strapiClient;

@observable
accessor tagMap: Record<string, Organization[]> = {};

async groupAllByTags(): Promise<Record<string, Organization[]>> {
try {
const allData = await this.getAll();
const tagMap: Record<string, Organization[]> = {};

for (const org of allData) {
const tags = org.tags || [];
for (const tag of tags) {
if (!tagMap[tag]) {
tagMap[tag] = [];
}
tagMap[tag].push(org);
}
}

this.tagMap = tagMap;
return tagMap;
} catch (error) {
console.error('Failed to fetch organizations:', error);
return {};
}
}
}

export class OrganizationStatisticModel {
private client: HTTPClient<any>;
private collection: string;

constructor(baseId: string, collectionId: string) {
this.client = new HTTPClient({
baseURI: 'https://china-ngo-db.onrender.com/api/',
responseType: 'json',
});
this.collection = collectionId;
}

async countAll(): Promise<Array<{ label: string; count: number }>> {
try {
// This would need to be adapted based on the actual Strapi API structure
const response = await this.client.get(`${this.collection}`);
// Handle potential different response structures
const data = response.body?.data || response.body || [];
return Array.isArray(data) ? data : [];
} catch (error) {
console.error(`Failed to fetch statistics for ${this.collection}:`, error);
return [];
}
}
}

// Mock constants for now - these would be configured based on the actual Strapi setup
export const COMMUNITY_BASE_ID = 'community';
export const OSC_YEAR_STATISTIC_TABLE_ID = 'organization-year-stats';
export const OSC_CITY_STATISTIC_TABLE_ID = 'organization-city-stats';
export const OSC_TYPE_STATISTIC_TABLE_ID = 'organization-type-stats';
export const OSC_TAG_STATISTIC_TABLE_ID = 'organization-tag-stats';
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"mobx": "^6.13.7",
"mobx-github": "^0.5.1",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.4.1",
"mobx-strapi": "^0.7.0",
"mobx-react": "^9.2.0",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.0",
Expand Down
Loading