Skip to content

Commit 48c5bf3

Browse files
committed
[add] Evaluation model & form
[optimize] update Upstream packages Signed-off-by: TechQuery <shiy2008@gmail.com>
1 parent 458ad57 commit 48c5bf3

File tree

6 files changed

+1376
-1196
lines changed

6 files changed

+1376
-1196
lines changed

components/Team/EvaluationForm.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Loading } from 'idea-react';
2+
import { computed } from 'mobx';
3+
import { observer } from 'mobx-react';
4+
import { ObservedComponent } from 'mobx-react-helper';
5+
import { FormField, RangeInput } from 'mobx-restful-table';
6+
import { FormEvent } from 'react';
7+
import { Button, Form } from 'react-bootstrap';
8+
import { formToJSON } from 'web-utility';
9+
10+
import { EvaluationModel } from '../../models/Activity/Evaluation';
11+
import { i18n, I18nContext } from '../../models/Base/Translation';
12+
import sessionStore from '../../models/User/Session';
13+
14+
export interface EvaluationFormProps {
15+
activityName: string;
16+
teamId: number;
17+
}
18+
19+
@observer
20+
export class EvaluationForm extends ObservedComponent<EvaluationFormProps, typeof i18n> {
21+
static contextType = I18nContext;
22+
23+
evaluationStore = new EvaluationModel(this.props.activityName, this.props.teamId);
24+
25+
@computed
26+
get evaluatable() {
27+
return !!sessionStore.user && !this.evaluationStore.currentPage[0];
28+
}
29+
30+
async componentDidMount() {
31+
await this.evaluationStore.getStandard();
32+
33+
const { user } = sessionStore;
34+
35+
if (user)
36+
await this.evaluationStore.getList({ createdBy: user.id, team: this.props.teamId }, 1);
37+
}
38+
39+
handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
40+
event.preventDefault();
41+
event.stopPropagation();
42+
43+
const form = formToJSON(event.currentTarget);
44+
45+
await this.evaluationStore.updateOne(form);
46+
47+
alert('Evaluation submitted successfully!');
48+
};
49+
50+
render() {
51+
const { t } = this.observedContext,
52+
{ evaluatable } = this;
53+
const { downloading, uploading, currentStandard, currentPage } = this.evaluationStore;
54+
const { dimensions } = currentStandard,
55+
[{ scores } = {}] = currentPage;
56+
const loading = downloading > 0 || uploading > 0;
57+
58+
return (
59+
<Form className="d-flex flex-column gap-3" onSubmit={this.handleSubmit}>
60+
{loading && <Loading />}
61+
62+
{dimensions.map(({ name, description, maximuScore }) => {
63+
const { score, reason } = scores?.find(({ dimension }) => dimension === name) || {};
64+
65+
return (
66+
<fieldset key={name} name="scores">
67+
<legend>{name}</legend>
68+
<p>{description}</p>
69+
70+
<input type="hidden" name="dimension" value={name} />
71+
<RangeInput
72+
className="text-warning"
73+
icon={value => (value ? '★' : '☆')}
74+
name="score"
75+
max={maximuScore}
76+
required
77+
disabled={!evaluatable}
78+
defaultValue={score?.toString()}
79+
/>
80+
<FormField
81+
label={t('judges_review')}
82+
as="textarea"
83+
rows={3}
84+
disabled={!evaluatable}
85+
defaultValue={reason}
86+
/>
87+
</fieldset>
88+
);
89+
})}
90+
<Button type="submit" variant="primary" disabled={!evaluatable}>
91+
{t('submit')}
92+
</Button>
93+
</Form>
94+
);
95+
}
96+
}

models/Activity/Evaluation.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Evaluation, Standard } from '@kaiyuanshe/openhackathon-service';
2+
import { observable } from 'mobx';
3+
import { persist, restore, toggle } from 'mobx-restful';
4+
5+
import { isServer } from '../../configuration';
6+
import { TableModel } from '../Base';
7+
8+
export class EvaluationModel extends TableModel<Evaluation> {
9+
baseURI = '';
10+
11+
constructor(activityName: string, teamId: number) {
12+
super();
13+
this.baseURI = `hackathon/${activityName}/team/${teamId}/evaluation`;
14+
}
15+
16+
@persist()
17+
@observable
18+
accessor currentStandard = {} as Standard;
19+
20+
restored = !isServer() && restore(this, 'Evaluation');
21+
22+
@toggle('downloading')
23+
async getStandard() {
24+
await this.restored;
25+
26+
if (this.currentStandard) return this.currentStandard;
27+
28+
const { body } = await this.client.get<Standard>(`${this.baseURI}/../../standard`);
29+
30+
return (this.currentStandard = body!);
31+
}
32+
}

models/Activity/Team.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { createListStream, Filter, InputData, TableModel } from '../Base';
1515
import { WorkspaceModel } from '../Git';
1616
import sessionStore from '../User/Session';
1717
import { AwardAssignment } from './Award';
18+
import { EvaluationModel } from './Evaluation';
1819

1920
export type TeamFilter = Filter<Team> & BaseFilter;
2021

@@ -63,6 +64,13 @@ export class TeamModel extends TableModel<Team, TeamFilter> {
6364
return (this.currentWorkspace = new WorkspaceModel(`${this.baseURI}/${tid}`));
6465
}
6566

67+
@computed
68+
get currentEvaluation() {
69+
const { hackathon, id } = this.currentOne;
70+
71+
return new EvaluationModel(hackathon.name, id);
72+
}
73+
6674
assignmentOf(tid = this.currentOne.id) {
6775
return (this.currentAssignment = new TeamAssignmentModel(`${this.baseURI}/${tid}`));
6876
}

package.json

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@fortawesome/free-solid-svg-icons": "^6.7.2",
1414
"@fortawesome/react-fontawesome": "^0.2.2",
1515
"@giscus/react": "^3.1.0",
16-
"@sentry/nextjs": "^9.24.0",
16+
"@sentry/nextjs": "^9.33.0",
1717
"array-unique-proposal": "^0.3.4",
1818
"classnames": "^2.5.1",
1919
"echarts-jsx": "^0.5.4",
@@ -31,8 +31,8 @@
3131
"mobx-react-helper": "^0.4.1",
3232
"mobx-restful": "^2.1.0",
3333
"mobx-restful-table": "^2.5.2",
34-
"next": "^15.3.3",
35-
"next-ssr-middleware": "^1.0.0",
34+
"next": "^15.3.4",
35+
"next-ssr-middleware": "^1.0.1",
3636
"open-react-map": "^0.9.0",
3737
"react": "^19.1.0",
3838
"react-bootstrap": "^2.10.10",
@@ -42,28 +42,28 @@
4242
"web-utility": "^4.4.3"
4343
},
4444
"devDependencies": {
45-
"@babel/core": "^7.27.4",
45+
"@babel/core": "^7.27.7",
4646
"@babel/plugin-proposal-decorators": "^7.27.1",
4747
"@babel/preset-react": "^7.27.1",
4848
"@babel/preset-typescript": "^7.27.1",
49-
"@cspell/eslint-plugin": "^9.0.2",
50-
"@eslint/compat": "^1.2.9",
49+
"@cspell/eslint-plugin": "^9.1.2",
50+
"@eslint/compat": "^1.3.1",
5151
"@eslint/eslintrc": "^3.3.1",
52-
"@eslint/js": "^9.28.0",
53-
"@kaiyuanshe/openhackathon-service": "^0.22.0",
54-
"@next/eslint-plugin-next": "^15.3.3",
52+
"@eslint/js": "^9.30.0",
53+
"@kaiyuanshe/openhackathon-service": "^1.0.0-rc.0",
54+
"@next/eslint-plugin-next": "^15.3.4",
5555
"@octokit/openapi-types": "^25.1.0",
5656
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
57-
"@stylistic/eslint-plugin": "^4.4.0",
57+
"@stylistic/eslint-plugin": "^5.1.0",
5858
"@types/eslint-config-prettier": "^6.11.3",
59-
"@types/jsonwebtoken": "^9.0.9",
59+
"@types/jsonwebtoken": "^9.0.10",
6060
"@types/koa": "^2.15.0",
61-
"@types/leaflet": "^1.9.18",
61+
"@types/leaflet": "^1.9.19",
6262
"@types/next-pwa": "^5.6.9",
63-
"@types/node": "^22.15.29",
64-
"@types/react": "^19.1.6",
65-
"eslint": "^9.28.0",
66-
"eslint-config-next": "^15.3.3",
63+
"@types/node": "^22.15.34",
64+
"@types/react": "^19.1.8",
65+
"eslint": "^9.30.0",
66+
"eslint-config-next": "^15.3.4",
6767
"eslint-config-prettier": "^10.1.5",
6868
"eslint-plugin-react": "^7.37.5",
6969
"eslint-plugin-simple-import-sort": "^12.1.1",
@@ -73,13 +73,13 @@
7373
"jiti": "^2.4.2",
7474
"less": "^4.3.0",
7575
"less-loader": "^12.3.0",
76-
"lint-staged": "^16.1.0",
76+
"lint-staged": "^16.1.2",
7777
"next-pwa": "^5.6.0",
7878
"next-with-less": "^3.0.1",
79-
"prettier": "^3.5.3",
79+
"prettier": "^3.6.2",
8080
"prettier-plugin-css-order": "^2.1.2",
8181
"typescript": "~5.8.3",
82-
"typescript-eslint": "^8.33.0",
82+
"typescript-eslint": "^8.35.0",
8383
"webpack": "^5.99.9"
8484
},
8585
"pnpm": {

pages/activity/[name]/team/[tid]/index.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { formToJSON } from 'web-utility';
1313
import { CommentBox } from '../../../../../components/CommentBox';
1414
import { MainBreadcrumb } from '../../../../../components/layout/MainBreadcrumb';
1515
import { PageHead } from '../../../../../components/layout/PageHead';
16+
import { EvaluationForm } from '../../../../../components/Team/EvaluationForm';
1617
import { JoinTeamModal } from '../../../../../components/Team/JoinTeamModal';
1718
import { TeamMemberListLayout } from '../../../../../components/Team/TeamMemberList';
1819
import { TeamWorkList } from '../../../../../components/Team/TeamWorkList';
@@ -91,6 +92,14 @@ export default class TeamPage extends ObservedComponent<TeamPageProps, typeof i1
9192
return activityStore.currentTeam?.sessionOne?.id === this.observedProps.team.id;
9293
}
9394

95+
@computed
96+
get evaluatable() {
97+
const now = Date.now(),
98+
{ judgeStartedAt, judgeEndedAt } = this.observedProps.activity;
99+
100+
return +new Date(judgeStartedAt) <= now && now <= +new Date(judgeEndedAt);
101+
}
102+
94103
async componentDidMount() {
95104
if (isServer()) return;
96105

@@ -161,6 +170,7 @@ export default class TeamPage extends ObservedComponent<TeamPageProps, typeof i1
161170
handleJoinTeam,
162171
isShowJoinTeamBtn,
163172
isShowLeaveTeamBtn,
173+
evaluatable,
164174
} = this;
165175

166176
return (
@@ -231,6 +241,8 @@ export default class TeamPage extends ObservedComponent<TeamPageProps, typeof i1
231241
</Tabs>
232242
</Col>
233243
</Row>
244+
{evaluatable && <EvaluationForm activityName={name} teamId={id} />}
245+
234246
<CommentBox />
235247

236248
<JoinTeamModal

0 commit comments

Comments
 (0)