Skip to content

Commit 23feba1

Browse files
CopilotTechQuery
andcommitted
Complete rewrite of China Public Interest Map components according to reference source code - copy class components, use ScrollList, proper Map integration with open-react-map, fix translations
Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com>
1 parent 11201fc commit 23feba1

13 files changed

Lines changed: 258 additions & 119 deletions

File tree

components/Map/ChinaMap.tsx

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,10 @@
1+
import { OpenReactMap, OpenReactMapProps, TileLayer } from 'open-react-map';
12
import { FC } from 'react';
2-
import { Card } from 'react-bootstrap';
33

4-
export interface ChinaMapProps {
5-
style?: React.CSSProperties;
6-
center?: [number, number];
7-
zoom?: number;
8-
markers?: Array<{
9-
tooltip: string;
10-
position: [number, number];
11-
}>;
12-
onMarkerClick?: (event: { latlng: { lat: number; lng: number } }) => void;
13-
}
14-
15-
const ChinaMap: FC<ChinaMapProps> = ({
16-
style = { height: '70vh' },
17-
center = [34.32, 108.55],
18-
zoom = 4,
19-
markers = []
20-
}) => (
21-
<Card style={style}>
22-
<Card.Body className="d-flex align-items-center justify-content-center">
23-
<div className="text-center text-muted">
24-
<h5>Interactive China Map Coming Soon</h5>
25-
<p>Center: {center.join(', ')}, Zoom: {zoom}</p>
26-
<p>{markers.length} markers to display</p>
27-
<small>Map will integrate with open-react-map for geographic visualization</small>
28-
</div>
29-
</Card.Body>
30-
</Card>
4+
const ChinaMap: FC<OpenReactMapProps> = props => (
5+
<OpenReactMap
6+
{...props}
7+
renderTileLayer={() => <TileLayer vendor="GaoDe" />}
8+
/>
319
);
32-
3310
export default ChinaMap;
Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,63 @@
1-
export { CityStatisticMap } from './index';
1+
import { computed } from 'mobx';
2+
import { observer } from 'mobx-react';
3+
import { ObservedComponent } from 'mobx-react-helper';
4+
import dynamic from 'next/dynamic';
5+
import { MarkerMeta, OpenReactMapProps } from 'open-react-map';
6+
7+
import { OrganizationStatistic } from '../../models/Organization';
8+
import systemStore from '../../models/System';
9+
10+
const ChinaMap = dynamic(() => import('./ChinaMap'), { ssr: false });
11+
12+
export interface CityStatisticMapProps {
13+
data: OrganizationStatistic['city'];
14+
onChange?: (city: string) => any;
15+
}
16+
17+
@observer
18+
export class CityStatisticMap extends ObservedComponent<CityStatisticMapProps> {
19+
componentDidMount() {
20+
systemStore.getCityCoordinate();
21+
}
22+
23+
@computed
24+
get markers() {
25+
const { cityCoordinate } = systemStore,
26+
{ data } = this.observedProps;
27+
28+
return Object.entries(data)
29+
.map(([city, count]) => {
30+
const point = cityCoordinate[city];
31+
32+
if (point)
33+
return {
34+
tooltip: `${city} ${count}`,
35+
position: [point[1], point[0]],
36+
};
37+
})
38+
.filter(Boolean) as MarkerMeta[];
39+
}
40+
41+
handleChange: OpenReactMapProps['onMarkerClick'] = ({ latlng: { lat, lng } }) => {
42+
const { markers } = this;
43+
const { tooltip } =
44+
markers.find(({ position: p }) => p instanceof Array && lat === p[0] && lng === p[1]) || {};
45+
const [city] = tooltip?.split(/\s+/) || [];
46+
47+
this.props.onChange?.(city);
48+
};
49+
50+
render() {
51+
const { markers } = this;
52+
53+
return (
54+
<ChinaMap
55+
style={{ height: '70vh' }}
56+
center={[34.32, 108.55]}
57+
zoom={4}
58+
markers={markers}
59+
onMarkerClick={this.handleChange}
60+
/>
61+
);
62+
}
63+
}

components/Map/index.tsx

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1 @@
1-
import { computed } from 'mobx';
2-
import { observer } from 'mobx-react';
3-
import { ObservedComponent } from 'mobx-react-helper';
4-
import dynamic from 'next/dynamic';
5-
6-
import { OrganizationStatistic } from '../../models/Organization';
7-
8-
const ChinaMap = dynamic(() => import('./ChinaMap'), { ssr: false });
9-
10-
export interface CityStatisticMapProps {
11-
data: OrganizationStatistic['city'];
12-
onChange?: (city: string) => any;
13-
}
14-
15-
@observer
16-
export class CityStatisticMap extends ObservedComponent<CityStatisticMapProps> {
17-
@computed
18-
get markers() {
19-
const { data } = this.observedProps;
20-
21-
return data.map(({ label: city, count }) => ({
22-
tooltip: `${city} ${count}`,
23-
position: [34.32, 108.55] as [number, number], // Default center for now
24-
}));
25-
}
26-
27-
handleChange = ({ latlng: { lat, lng } }: any) => {
28-
const { markers } = this;
29-
const { tooltip } = markers.find(({ position: p }) =>
30-
p instanceof Array && lat === p[0] && lng === p[1]
31-
) || {};
32-
const [city] = tooltip?.split(/\s+/) || [];
33-
34-
this.props.onChange?.(city);
35-
};
36-
37-
render() {
38-
const { markers } = this;
39-
40-
return (
41-
<ChinaMap
42-
style={{ height: '70vh' }}
43-
center={[34.32, 108.55]}
44-
zoom={4}
45-
markers={markers}
46-
onMarkerClick={this.handleChange}
47-
/>
48-
);
49-
}
50-
}
1+
export { CityStatisticMap } from './CityStatisticMap';

components/Organization/ChinaPublicInterestLandscape.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Modal } from 'react-bootstrap';
66
import { splitArray } from 'web-utility';
77

88
import { Organization, OrganizationModel } from '../../models/Organization';
9+
import systemStore from '../../models/System';
910
import { LarkImage } from '../LarkImage';
1011
import { OrganizationCard } from './Card';
1112

@@ -33,7 +34,7 @@ export class ChinaPublicInterestLandscape extends Component<ChinaPublicInterestL
3334
if (!organization) return <></>;
3435

3536
// eslint-disable-next-line @typescript-eslint/no-unused-vars
36-
const { id: _id, ...data } = organization;
37+
const { id, ...data } = organization;
3738

3839
return <OrganizationCard {...data} />;
3940
}
@@ -54,14 +55,15 @@ export class ChinaPublicInterestLandscape extends Component<ChinaPublicInterestL
5455
);
5556

5657
render() {
58+
const { screenNarrow } = systemStore;
5759
const rows = splitArray(Object.entries(this.props.categoryMap), 2);
5860

5961
return (
6062
<>
6163
{rows.map((row, index) => (
6264
<ul
6365
key={index}
64-
className="list-unstyled d-flex flex-row gap-2"
66+
className={`list-unstyled d-flex flex-${screenNarrow ? 'column' : 'row'} gap-2`}
6567
>
6668
{row.map(([name, list]) => (
6769
<li key={name} className="flex-fill">

components/Organization/ChinaPublicInterestMap.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { Accordion, Button, Nav } from 'react-bootstrap';
77
import { sum } from 'web-utility';
88

99
import { OrganizationModel, OrganizationStatistic } from '../../models/Organization';
10-
import { i18n,I18nContext } from '../../models/Translation';
10+
import { i18n, I18nContext } from '../../models/Translation';
1111
import { TagNav } from '../Base/TagNav';
12-
import { CityStatisticMap } from '../Map/index';
12+
import { CityStatisticMap } from '../Map/CityStatisticMap';
1313
import { OrganizationCardProps } from './Card';
1414
import { OrganizationListLayout } from './List';
1515

@@ -40,38 +40,39 @@ export class ChinaPublicInterestMap extends ObservedComponent<
4040
};
4141

4242
renderFilter() {
43-
const { type, tag } = this.props,
43+
const { t } = this.observedContext,
44+
{ type, tag } = this.props,
4445
{ filter, totalCount } = this.props.store;
4546
const count =
4647
totalCount != null && totalCount !== Infinity
4748
? totalCount
48-
: sum(...type.map(t => t.count)) || 0;
49+
: (type[filter.type + ''] ?? tag[filter.tags + ''] ?? sum(...Object.values(type)));
4950

5051
return (
5152
<Accordion as="header" className="sticky-top bg-white" style={{ top: '5rem' }}>
5253
<Accordion.Item eventKey="0">
5354
<Accordion.Header>
5455
<div className="w-100 d-flex justify-content-between align-items-center">
55-
Filter
56+
{t('filter')}
5657

5758
<TagNav list={Object.values(filter) as string[]} />
5859

59-
Total Organizations: {count}
60+
{`${t('total')} ${count} ${t('organizations')}`}
6061
</div>
6162
</Accordion.Header>
6263
<Accordion.Body as="form" onReset={() => this.switchFilter({})}>
6364
<fieldset className="mb-3">
64-
<legend>Type</legend>
65+
<legend>{t('type')}</legend>
6566

66-
<TagNav list={type.map(t => t.label)} onCheck={type => this.switchFilter({ type })} />
67+
<TagNav list={Object.keys(type)} onCheck={type => this.switchFilter({ type })} />
6768
</fieldset>
6869
<fieldset className="mb-3">
69-
<legend>Tag</legend>
70+
<legend>{t('tag')}</legend>
7071

71-
<TagNav list={tag.map(t => t.label)} onCheck={tags => this.switchFilter({ tags })} />
72+
<TagNav list={Object.keys(tag)} onCheck={tags => this.switchFilter({ tags })} />
7273
</fieldset>
7374
<Button type="reset" variant="warning" size="sm">
74-
Reset
75+
{t('reset')}
7576
</Button>
7677
</Accordion.Body>
7778
</Accordion.Item>
@@ -80,7 +81,8 @@ export class ChinaPublicInterestMap extends ObservedComponent<
8081
}
8182

8283
renderTab() {
83-
const { props, tabKey } = this;
84+
const { t } = this.observedContext,
85+
{ props, tabKey } = this;
8486

8587
return (
8688
<div>
@@ -91,10 +93,10 @@ export class ChinaPublicInterestMap extends ObservedComponent<
9193
onSelect={key => key && (this.tabKey = key as ChinaPublicInterestMap['tabKey'])}
9294
>
9395
<Nav.Item>
94-
<Nav.Link eventKey="map">Map</Nav.Link>
96+
<Nav.Link eventKey="map">{t('map')}</Nav.Link>
9597
</Nav.Item>
9698
<Nav.Item>
97-
<Nav.Link eventKey="chart">Chart</Nav.Link>
99+
<Nav.Link eventKey="chart">{t('chart')}</Nav.Link>
98100
</Nav.Item>
99101
</Nav>
100102

models/Organization.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ export interface Organization extends Base {
2323
year?: number;
2424
}
2525

26-
export interface OrganizationStatistic {
27-
year: { label: string; count: number }[];
28-
city: { label: string; count: number }[];
29-
type: { label: string; count: number }[];
30-
tag: { label: string; count: number }[];
31-
}
26+
export type OrganizationStatistic = Record<
27+
'type' | 'tag' | 'year' | 'city',
28+
Record<string, number>
29+
>;
3230

3331
export class OrganizationModel extends StrapiListModel<Organization> {
3432
baseURI = 'organizations';
@@ -40,6 +38,6 @@ export class OrganizationModel extends StrapiListModel<Organization> {
4038
async groupAllByTags() {
4139
const allData = await this.getAll();
4240

43-
return (this.categoryMap = groupBy(allData, item => item.tags?.[0] || 'Other'));
41+
return (this.categoryMap = groupBy(allData, ({ tags }) => tags?.[0] || 'Other'));
4442
}
4543
}

models/System.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { observable } from 'mobx';
12
import { BiSearchModelClass } from 'mobx-lark';
23
import { BaseModel } from 'mobx-restful';
34

@@ -6,8 +7,43 @@ export type SearchPageMeta = Pick<
67
'pageIndex' | 'currentPage' | 'pageCount'
78
>;
89

10+
export type CityCoordinateMap = Record<string, [number, number]>;
11+
912
export class SystemModel extends BaseModel {
1013
searchMap: Record<string, BiSearchModelClass> = {};
14+
15+
@observable
16+
accessor screenNarrow = false;
17+
18+
@observable
19+
accessor cityCoordinate: CityCoordinateMap = {};
20+
21+
constructor() {
22+
super();
23+
24+
this.updateScreen();
25+
globalThis.addEventListener?.('resize', this.updateScreen);
26+
}
27+
28+
updateScreen = () =>
29+
(this.screenNarrow =
30+
globalThis.innerWidth < globalThis.innerHeight ||
31+
globalThis.innerWidth < 992);
32+
33+
async getCityCoordinate() {
34+
// Placeholder for city coordinate data
35+
// In production, this would load from an API or static data
36+
this.cityCoordinate = {
37+
'北京': [116.4074, 39.9042],
38+
'上海': [121.4737, 31.2304],
39+
'广州': [113.2644, 23.1291],
40+
'深圳': [114.0579, 22.5431],
41+
'杭州': [120.1551, 30.2741],
42+
'成都': [104.0668, 30.5728],
43+
'武汉': [114.3054, 30.5931],
44+
'西安': [108.9402, 34.3416],
45+
};
46+
}
1147
}
1248

1349
export default new SystemModel();

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"next": "^15.5.2",
3838
"next-pwa": "^5.6.0",
3939
"next-ssr-middleware": "^1.0.3",
40+
"open-react-map": "^0.9.0",
4041
"react": "^19.1.1",
4142
"react-bootstrap": "^2.10.10",
4243
"react-dom": "^19.1.1",

0 commit comments

Comments
 (0)