Skip to content

Commit c4315bf

Browse files
committed
[add] Governance models, components & pages migrated from KYS project committee
[optimize] Folder Structure of Source Code
1 parent a810ecc commit c4315bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3689
-268
lines changed

components/Base/CommentBox.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Giscus, { GiscusProps } from '@giscus/react';
2+
import { observer } from 'mobx-react';
3+
import { FC } from 'react';
4+
5+
import { i18n } from '../../models/Translation';
6+
7+
export const CommentBox: FC<Partial<GiscusProps>> = observer(props => {
8+
const { currentLanguage } = i18n;
9+
10+
return (
11+
<Giscus
12+
{...props}
13+
repo="kaiyuanshe/kaiyuanshe.github.io"
14+
repoId="MDEwOlJlcG9zaXRvcnkxMzEwMDg4MTI="
15+
mapping="pathname"
16+
reactionsEnabled="1"
17+
emitMetadata="1"
18+
inputPosition="bottom"
19+
theme="light"
20+
lang={currentLanguage?.startsWith('zh-') ? currentLanguage : currentLanguage?.split('-')[0]}
21+
/>
22+
);
23+
});

components/Base/FileList.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { text2color } from 'idea-react';
2+
import { TableCellAttachment } from 'mobx-lark';
3+
import { observer } from 'mobx-react';
4+
import { FilePreview } from 'mobx-restful-table';
5+
import { FC, useContext } from 'react';
6+
import { Badge } from 'react-bootstrap';
7+
8+
import { I18nContext } from '../../models/Translation';
9+
10+
export const FileList: FC<{ data: TableCellAttachment[] }> = observer(({ data }) => {
11+
const { t } = useContext(I18nContext);
12+
13+
return (
14+
<section>
15+
<h2>{t('file_download')}</h2>
16+
<ol className="mt-3 mb-5">
17+
{data.map(({ id, name, mimeType, attachmentToken }) => (
18+
<li key={id + ''}>
19+
<FilePreview type={mimeType} path={`/api/lark/file/${attachmentToken}`} />
20+
21+
<Badge bg={text2color(name, ['light'])}>{name}</Badge>
22+
</li>
23+
))}
24+
</ol>
25+
</section>
26+
);
27+
});

components/LarkImage.tsx renamed to components/Base/LarkImage.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@ import { TableCellValue } from 'mobx-lark';
22
import { FC } from 'react';
33
import { Image, ImageProps } from 'react-bootstrap';
44

5-
import { fileURLOf } from '../models/Base';
6-
import { DefaultImage } from '../models/configuration';
5+
import { DefaultImage } from '../../utility/configuration';
6+
import { fileURLOf } from '../../utility/Lark';
77

88
export interface LarkImageProps extends Omit<ImageProps, 'src'> {
99
src?: TableCellValue;
1010
}
1111

12-
export const LarkImage: FC<LarkImageProps> = ({
13-
src = DefaultImage,
14-
alt,
15-
...props
16-
}) => (
12+
export const LarkImage: FC<LarkImageProps> = ({ src = DefaultImage, alt, ...props }) => (
1713
<Image
1814
fluid
1915
loading="lazy"

components/Base/TagNav.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { text2color } from 'idea-react';
2+
import { FC, HTMLAttributes } from 'react';
3+
import { Badge } from 'react-bootstrap';
4+
5+
export interface TagNavProps extends HTMLAttributes<HTMLElement> {
6+
linkOf?: (value: string) => string;
7+
list: string[];
8+
onCheck?: (value: string) => any;
9+
}
10+
11+
export const TagNav: FC<TagNavProps> = ({ className = '', list, linkOf, onCheck, ...props }) => (
12+
<nav className={`d-flex flex-wrap gap-2 ${className}`} {...props}>
13+
{list.map(tag => (
14+
<Badge
15+
key={tag + ''}
16+
as="a"
17+
className={`text-decoration-none ${onCheck ? 'cursor-pointer' : ''}`}
18+
bg={text2color(tag + '', ['light'])}
19+
href={linkOf?.(tag)}
20+
onClick={onCheck && (() => onCheck(tag))}
21+
>
22+
{tag + ''}
23+
</Badge>
24+
))}
25+
</nav>
26+
);

components/Base/ZodiacBar.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Link from 'next/link';
2+
import { FC, ReactNode } from 'react';
3+
4+
export const ZodiacSigns = ['🐵', '🐔', '🐶', '🐷', '🐭', '🐮', '🐯', '🐰', '🐲', '🐍', '🐴', '🐐'];
5+
6+
export interface ZodiacBarProps {
7+
startYear: number;
8+
endYear?: number;
9+
itemOf?: (year: number, zodiac: string) => { link?: string; title?: ReactNode };
10+
}
11+
12+
export const ZodiacBar: FC<ZodiacBarProps> = ({
13+
startYear,
14+
endYear = new Date().getFullYear(),
15+
itemOf,
16+
}) => (
17+
<ol className="list-inline d-flex flex-wrap justify-content-center gap-3">
18+
{Array.from({ length: endYear - startYear + 1 }, (_, index) => {
19+
const year = endYear - index;
20+
const zodiac = ZodiacSigns[year % 12];
21+
const { link = '#', title } = itemOf?.(year, zodiac) || {};
22+
23+
return (
24+
<li key={index} className="list-inline-item border rounded">
25+
<Link className="d-inline-block p-3 text-decoration-none text-center" href={link}>
26+
<div className="fs-1">{zodiac}</div>
27+
28+
{title}
29+
</Link>
30+
</li>
31+
);
32+
})}
33+
</ol>
34+
);

components/Department/Card.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { FC } from 'react';
2+
3+
import { Department } from '../../models/Personnel/Department';
4+
import { LarkImage } from '../Base/LarkImage';
5+
import { TagNav } from '../Base/TagNav';
6+
7+
export interface GroupCardProps
8+
extends Pick<Department, 'name' | 'logo' | 'tags' | 'summary' | 'email'> {
9+
className?: string;
10+
}
11+
12+
export const GroupCard: FC<GroupCardProps> = ({
13+
className = '',
14+
name,
15+
logo,
16+
tags,
17+
summary,
18+
email,
19+
}) => (
20+
<div className={`d-flex flex-column align-items-center ${className}`}>
21+
<h3 className="h5 mb-3 flex-fill">
22+
<a className="text-decoration-none text-dark" href={`/department/${name}`}>
23+
{name as string}
24+
</a>
25+
</h3>
26+
27+
{logo && (
28+
<LarkImage
29+
className="mb-3 flex-fill object-fit-contain"
30+
style={{ maxWidth: '10rem' }}
31+
src={logo}
32+
alt={name as string}
33+
/>
34+
)}
35+
{tags && (
36+
<TagNav linkOf={value => `/search/department?keywords=${value}`} list={tags as string[]} />
37+
)}
38+
{email && (
39+
<dl className="mt-1 d-flex align-items-start">
40+
<dt className="me-1">E-mail:</dt>
41+
<dd>
42+
<a href={`mailto:${email}`}>{email as string}</a>
43+
</dd>
44+
</dl>
45+
)}
46+
<p
47+
className="mt-3 mb-0 text-wrap text-start overflow-auto"
48+
style={{ maxWidth: '50vw', maxHeight: '10rem' }}
49+
>
50+
{summary as string}
51+
</p>
52+
</div>
53+
);

components/Department/OKRCard.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { text2color } from 'idea-react';
2+
import { textJoin } from 'mobx-i18n';
3+
import { observer } from 'mobx-react';
4+
import { FC, useContext } from 'react';
5+
import { Badge, Card } from 'react-bootstrap';
6+
import { formatDate } from 'web-utility';
7+
8+
import { OKR } from '../../models/Governance/OKR';
9+
import { I18nContext } from '../../models/Translation';
10+
11+
export const OKRCard: FC<OKR> = observer(
12+
({
13+
createdAt,
14+
department,
15+
object,
16+
firstResult,
17+
secondResult,
18+
thirdResult,
19+
planQ1,
20+
planQ2,
21+
planQ3,
22+
planQ4,
23+
}) => {
24+
const { t } = useContext(I18nContext);
25+
26+
return (
27+
<Card>
28+
<Card.Header as="h3">{object?.toString()}</Card.Header>
29+
30+
<Card.Body className="overflow-auto" style={{ maxHeight: '25rem' }}>
31+
<Card.Title as="h4">{t('key_results')}</Card.Title>
32+
<Card.Text>
33+
<ol>
34+
{[firstResult, secondResult, thirdResult].map(
35+
result => result && <li key={result + ''}>{result + ''}</li>,
36+
)}
37+
</ol>
38+
</Card.Text>
39+
<Card.Title as="h4">{textJoin(t('quarterly'), t('plan'))}</Card.Title>
40+
<Card.Text>
41+
<ol className="list-unstyled">
42+
{[planQ1, planQ2, planQ3, planQ4].map((plan, index) => (
43+
<li key={index}>
44+
<Badge bg={text2color(`Q${index + 1}`, ['light'])}>Q{index + 1}</Badge>{' '}
45+
{plan?.toString()}
46+
</li>
47+
))}
48+
</ol>
49+
</Card.Text>
50+
</Card.Body>
51+
<Card.Footer className="d-flex justify-content-between align-items-center">
52+
<time dateTime={new Date(createdAt as number).toJSON()}>
53+
{formatDate(createdAt as number)}
54+
</time>
55+
<Badge bg={text2color(department + '', ['light'])}>{department + ''}</Badge>
56+
</Card.Footer>
57+
</Card>
58+
);
59+
},
60+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { text2color } from 'idea-react';
2+
import { observer } from 'mobx-react';
3+
import { FC, useContext } from 'react';
4+
import { Badge, Card } from 'react-bootstrap';
5+
import { formatDate } from 'web-utility';
6+
7+
import { Report } from '../../models/Governance/Report';
8+
import { I18nContext } from '../../models/Translation';
9+
10+
export const ReportCard: FC<Report> = observer(
11+
({ createdAt, department, plan, progress, product, problem, meeting }) => {
12+
const { t } = useContext(I18nContext);
13+
14+
return (
15+
<Card>
16+
<Card.Header as="h3">{meeting?.toString()}</Card.Header>
17+
<Card.Body as="dl" className="mb-0 overflow-auto" style={{ maxHeight: '25rem' }}>
18+
<Card.Title as="dt">{t('plan')}</Card.Title>
19+
<Card.Text as="dd" dangerouslySetInnerHTML={{ __html: plan?.toString() || '' }} />
20+
<Card.Title as="dt">{t('progress')}</Card.Title>
21+
<Card.Text as="dd" dangerouslySetInnerHTML={{ __html: progress?.toString() || '' }} />
22+
<Card.Title as="dt">{t('product')}</Card.Title>
23+
<Card.Text as="dd" dangerouslySetInnerHTML={{ __html: product?.toString() || '' }} />
24+
<Card.Title as="dt">{t('problem')}</Card.Title>
25+
<Card.Text as="dd" dangerouslySetInnerHTML={{ __html: problem?.toString() || '' }} />
26+
</Card.Body>
27+
<Card.Footer className="d-flex justify-content-between align-items-center">
28+
<time dateTime={new Date(createdAt as number).toJSON()}>
29+
{formatDate(createdAt as number)}
30+
</time>
31+
<Badge bg={text2color(department + '', ['light'])}>{department + ''}</Badge>
32+
</Card.Footer>
33+
</Card>
34+
);
35+
},
36+
);

components/Department/Tree.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { SVGCharts, Tooltip, TreeSeries } from 'echarts-jsx';
2+
import { Loading } from 'idea-react';
3+
import { observer } from 'mobx-react';
4+
import { ObservedComponent } from 'mobx-react-helper';
5+
import { Form } from 'react-bootstrap';
6+
import { renderToStaticMarkup } from 'react-dom/server';
7+
8+
import { DepartmentModel, DepartmentNode } from '../../models/Personnel/Department';
9+
import { i18n, I18nContext } from '../../models/Translation';
10+
import { GroupCard } from './Card';
11+
12+
@observer
13+
export default class DepartmentTree extends ObservedComponent<{}, typeof i18n> {
14+
static contextType = I18nContext;
15+
16+
store = new DepartmentModel();
17+
18+
componentDidMount() {
19+
this.store.getAll();
20+
}
21+
22+
renderGroup(name: string) {
23+
const group = this.store.allItems.find(({ name: n }) => n === name);
24+
25+
return renderToStaticMarkup(group?.summary ? <GroupCard {...group} /> : <></>);
26+
}
27+
28+
jumpLink({ name }: DepartmentNode) {
29+
if (name === '理事会') {
30+
location.href = '/department/board-of-directors';
31+
} else {
32+
location.href = `/department/${name}`;
33+
}
34+
}
35+
36+
render() {
37+
const { t } = this.observedContext,
38+
{ downloading, activeShown, tree } = this.store;
39+
40+
return (
41+
<>
42+
{downloading > 0 && <Loading />}
43+
44+
<label className="d-flex justify-content-center gap-3">
45+
{t('show_active_departments')}
46+
<Form.Switch checked={activeShown} onChange={this.store.toggleActive} />
47+
</label>
48+
49+
<SVGCharts style={{ height: '80vh' }}>
50+
<Tooltip trigger="item" triggerOn="mousemove" />
51+
52+
<TreeSeries
53+
label={{
54+
position: 'left',
55+
verticalAlign: 'middle',
56+
fontSize: 16,
57+
}}
58+
tooltip={{
59+
formatter: ({ name }) => this.renderGroup(name),
60+
}}
61+
data={[tree]}
62+
onClick={({ data }) => this.jumpLink(data as DepartmentNode)}
63+
/>
64+
</SVGCharts>
65+
</>
66+
);
67+
}
68+
}

0 commit comments

Comments
 (0)