Skip to content

Commit 9235830

Browse files
committed
Preparing plant term groups button.
1 parent 8d74b4a commit 9235830

File tree

12 files changed

+513
-151
lines changed

12 files changed

+513
-151
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { FormattedMessage } from 'react-intl';
4+
import { TermIcon } from '../../icons';
5+
6+
const TermLabel = ({
7+
term,
8+
icon = false,
9+
longNames = false,
10+
emphasize = false,
11+
iconClassName = null,
12+
yearClassName = null,
13+
termClassName = null,
14+
}) => {
15+
return (
16+
<>
17+
{icon && <TermIcon gapRight className={iconClassName || 'text-muted'} />}
18+
<span className={yearClassName || (emphasize ? 'fw-bold' : '')}>
19+
{term.year}-{term.term}
20+
</span>{' '}
21+
<span className={termClassName || (emphasize ? 'small text-muted ms-2' : '')}>
22+
(
23+
{longNames ? (
24+
<>
25+
{term.term === 1 && <FormattedMessage id="app.terms.winterLong" defaultMessage="Winter Term" />}
26+
{term.term === 2 && <FormattedMessage id="app.terms.summerLong" defaultMessage="Summer Term" />}
27+
</>
28+
) : (
29+
<>
30+
{term.term === 1 && <FormattedMessage id="app.terms.winter" defaultMessage="Winter" />}
31+
{term.term === 2 && <FormattedMessage id="app.terms.summer" defaultMessage="Summer" />}
32+
</>
33+
)}
34+
)
35+
</span>
36+
</>
37+
);
38+
};
39+
40+
TermLabel.propTypes = {
41+
term: PropTypes.shape({
42+
year: PropTypes.number.isRequired,
43+
term: PropTypes.number.isRequired,
44+
}).isRequired,
45+
icon: PropTypes.bool,
46+
longNames: PropTypes.bool,
47+
emphasize: PropTypes.bool,
48+
iconClassName: PropTypes.string,
49+
yearClassName: PropTypes.string,
50+
termClassName: PropTypes.string,
51+
};
52+
53+
export default TermLabel;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import TermLabel from './TermLabel.js';
2+
export default TermLabel;

src/components/forms/AddAttributeForm/AddAttributeForm.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ const validate = lruMemoize(attributes => values => {
7272
return errors;
7373
});
7474

75+
export const INITIAL_VALUES = {
76+
mode: 'course',
77+
course: '',
78+
term: '',
79+
group: '',
80+
key: '',
81+
value: '',
82+
};
83+
7584
const AddAttributeForm = ({ initialValues, onSubmit, onClose, attributes = EMPTY_OBJ }) => {
7685
return (
7786
<Form
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
import AddAttributeForm from './AddAttributeForm.js';
1+
import AddAttributeForm, { INITIAL_VALUES } from './AddAttributeForm.js';
22
export default AddAttributeForm;
3+
export { INITIAL_VALUES };
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Form, Field, FormSpy } from 'react-final-form';
4+
import { FormattedMessage } from 'react-intl';
5+
6+
import { CloseIcon, LoadingIcon, SaveIcon } from '../../icons';
7+
import Button, { TheButtonGroup } from '../../widgets/TheButton';
8+
import { TextField, StandaloneRadioField } from '../fields';
9+
import Explanation from '../../widgets/Explanation';
10+
import { lruMemoize } from 'reselect';
11+
import { EMPTY_OBJ } from '../../../helpers/common';
12+
import Callout from '../../widgets/Callout';
13+
14+
const empty = values => {
15+
const mode = values.mode === 'other' ? 'key' : values.mode;
16+
return !values[mode];
17+
};
18+
19+
const validate = lruMemoize(attributes => values => {
20+
const errors = {};
21+
if (values.mode === 'course') {
22+
if (values.course && !/^[A-Z0-9]{3,9}$/.test(values.course)) {
23+
errors.course = (
24+
<FormattedMessage
25+
id="app.addAttributeForm.validate.course"
26+
defaultMessage="Course identifier can contain only uppercase letters and digits and must have adequate length."
27+
/>
28+
);
29+
}
30+
} else if (values.mode === 'term') {
31+
if (values.term && !/^20[0-9]{2}-[12]$/.test(values.term)) {
32+
errors.term = (
33+
<FormattedMessage
34+
id="app.addAttributeForm.validate.term"
35+
defaultMessage="Semester must be in the format YYYY-T, where YYYY is the year and T is the term number (1-2)."
36+
/>
37+
);
38+
}
39+
} else if (values.mode === 'group') {
40+
if (values.group && !/^[a-zA-Z0-9]{8,16}$/.test(values.group)) {
41+
errors.group = (
42+
<FormattedMessage
43+
id="app.addAttributeForm.validate.group"
44+
defaultMessage="The identifier can contain only letters and digits and must be 8-16 characters long."
45+
/>
46+
);
47+
}
48+
} else if (values.mode === 'other') {
49+
if (values.key && !/^[-_a-zA-Z0-9]+$/.test(values.key)) {
50+
errors.key = (
51+
<FormattedMessage
52+
id="app.addAttributeForm.validate.key"
53+
defaultMessage="The key can contain only letters, digits, dash, and underscore."
54+
/>
55+
);
56+
}
57+
}
58+
59+
if (Object.keys(errors).length === 0) {
60+
const key = values.mode === 'other' ? values.key : values.mode;
61+
const value = values.mode === 'other' ? values.value : values[key];
62+
if (key && attributes && attributes[key] && attributes[key].includes(value)) {
63+
errors[values.mode === 'other' ? 'value' : key] = (
64+
<FormattedMessage
65+
id="app.addAttributeForm.validate.duplicate"
66+
defaultMessage="The attribute [{key}: {value}] is already associated with this group."
67+
values={{ key, value }}
68+
/>
69+
);
70+
}
71+
}
72+
return errors;
73+
});
74+
75+
export const INITIAL_VALUES = {};
76+
77+
const PlantTermGroupsForm = ({ initialValues, onSubmit, onClose, attributes = EMPTY_OBJ }) => {
78+
return (
79+
<Form
80+
onSubmit={onSubmit}
81+
initialValues={initialValues}
82+
validate={validate(attributes)}
83+
render={({ handleSubmit, submitting, submitError }) => (
84+
<form onSubmit={handleSubmit}>
85+
<table className="mb-2">
86+
<tbody>
87+
<FormSpy subscription={{ values: true }}>
88+
{({ values: { mode } }) => (
89+
<>
90+
<tr className={mode === 'course' ? 'bg-success bg-opacity-10' : ''}>
91+
<td className="align-middle ps-3">
92+
<StandaloneRadioField name="mode" value="course" />
93+
</td>
94+
<td colSpan={2} className="w-100 px-3">
95+
<Field
96+
component={TextField}
97+
name="course"
98+
ignoreDirty
99+
disabled={mode !== 'course'}
100+
maxLength={9}
101+
placeholder="NPRG001"
102+
label={
103+
<>
104+
<FormattedMessage id="app.addAttributeForm.course" defaultMessage="Course" />:
105+
<Explanation id="course-explanation">
106+
<FormattedMessage
107+
id="app.addAttributeForm.course.explanation"
108+
defaultMessage="Associating course identifier enables bindings and group creations for SIS events of that course in the whole sub-tree."
109+
/>
110+
</Explanation>
111+
</>
112+
}
113+
/>
114+
</td>
115+
</tr>
116+
117+
<tr className={mode === 'term' ? 'bg-success bg-opacity-10' : ''}>
118+
<td className="align-middle ps-3">
119+
<StandaloneRadioField name="mode" value="term" />
120+
</td>
121+
<td colSpan={2} className="w-100 px-3">
122+
<Field
123+
component={TextField}
124+
name="term"
125+
ignoreDirty
126+
disabled={mode !== 'term'}
127+
maxLength={6}
128+
placeholder="2025-1"
129+
label={
130+
<>
131+
<FormattedMessage id="app.addAttributeForm.term" defaultMessage="Semester" />:
132+
<Explanation id="term-explanation">
133+
<FormattedMessage
134+
id="app.addAttributeForm.term.explanation"
135+
defaultMessage="Associating term (semester) identifier enables bindings and group creations for SIS events of that term in the whole sub-tree."
136+
/>
137+
</Explanation>
138+
</>
139+
}
140+
/>
141+
</td>
142+
</tr>
143+
144+
<tr className={mode === 'group' ? 'bg-success bg-opacity-10' : ''}>
145+
<td className="align-middle ps-3">
146+
<StandaloneRadioField name="mode" value="group" />
147+
</td>
148+
<td colSpan={2} className="w-100 px-3">
149+
<Field
150+
component={TextField}
151+
name="group"
152+
ignoreDirty
153+
disabled={mode !== 'group'}
154+
maxLength={20}
155+
placeholder="25aNPRG058x01"
156+
label={
157+
<>
158+
<FormattedMessage id="app.addAttributeForm.group" defaultMessage="SIS Scheduling Event" />
159+
:
160+
<Explanation id="group-explanation">
161+
<FormattedMessage
162+
id="app.addAttributeForm.group.explanation"
163+
defaultMessage="Association between groups and SIS events is usually done by binding or creating new groups from SIS events. This circumvents traditional checks, so any SIS event ID can be associated with this group. Please, handle with extreme care."
164+
/>
165+
</Explanation>
166+
</>
167+
}
168+
/>
169+
</td>
170+
</tr>
171+
172+
<tr className={mode === 'other' ? 'bg-success bg-opacity-10' : ''}>
173+
<td className="align-middle ps-3">
174+
<StandaloneRadioField name="mode" value="other" />
175+
</td>
176+
<td className="w-50 ps-3">
177+
<Field
178+
component={TextField}
179+
name="key"
180+
ignoreDirty
181+
disabled={mode !== 'other'}
182+
maxLength={32}
183+
label={
184+
<>
185+
<FormattedMessage id="app.addAttributeForm.key" defaultMessage="Custom Key" />:
186+
<Explanation id="other-explanation">
187+
<FormattedMessage
188+
id="app.addAttributeForm.other.explanation"
189+
defaultMessage="Creating custom attributes is intended to simplify preparations for future features. Avoid creating attributes unless you are absolutely certain what you are doing."
190+
/>
191+
</Explanation>
192+
</>
193+
}
194+
/>
195+
</td>
196+
<td className="w-50 pe-3">
197+
<Field
198+
component={TextField}
199+
name="value"
200+
ignoreDirty
201+
disabled={mode !== 'other'}
202+
maxLength={250}
203+
label={
204+
<>
205+
<FormattedMessage id="app.addAttributeForm.value" defaultMessage="Value" />:
206+
</>
207+
}
208+
/>
209+
</td>
210+
</tr>
211+
</>
212+
)}
213+
</FormSpy>
214+
</tbody>
215+
</table>
216+
217+
<FormSpy subscription={{ errors: true }}>
218+
{({ errors: { students } }) => students && <Callout variant="danger">{students}</Callout>}
219+
</FormSpy>
220+
221+
{submitError && <Callout variant="danger">{submitError}</Callout>}
222+
223+
<div className="text-center">
224+
<TheButtonGroup>
225+
<FormSpy subscription={{ values: true, valid: true }}>
226+
{({ values, valid }) => (
227+
<Button type="submit" variant="success" disabled={submitting || !valid || empty(values)}>
228+
{submitting ? <LoadingIcon gapRight /> : <SaveIcon gapRight />}
229+
<FormattedMessage id="generic.create" defaultMessage="Create" />
230+
</Button>
231+
)}
232+
</FormSpy>
233+
234+
{onClose && (
235+
<Button onClick={onClose} variant="secondary" disabled={submitting}>
236+
<CloseIcon gapRight />
237+
<FormattedMessage id="generic.cancel" defaultMessage="Cancel" />
238+
</Button>
239+
)}
240+
</TheButtonGroup>
241+
</div>
242+
</form>
243+
)}
244+
/>
245+
);
246+
};
247+
248+
PlantTermGroupsForm.propTypes = {
249+
initialValues: PropTypes.object.isRequired,
250+
onSubmit: PropTypes.func.isRequired,
251+
onClose: PropTypes.func.isRequired,
252+
attributes: PropTypes.object,
253+
};
254+
255+
export default PlantTermGroupsForm;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import PlantTermGroupsForm, { INITIAL_VALUES } from './PlantTermGroupsForm.js';
2+
export default PlantTermGroupsForm;
3+
export { INITIAL_VALUES };

src/locales/cs.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@
8282
"app.groupMembershipIcon.supervisor": "Jste vedoucím této skupiny",
8383
"app.groups.coursesRefetched": "Seznam rozvrhových lístků byl právě znovu načten ze SIS",
8484
"app.groups.refreshButton": "Znovu načíst ze SIS",
85-
"app.groups.term.summer": "Letní semestr",
86-
"app.groups.term.winter": "Zimní semestr",
8785
"app.groupsStudent.lastRefreshInfo": "Seznam zapsaných rozvrhových lístků byl naposledy stažen ze SISu",
8886
"app.groupsStudent.noActiveTerms": "V tuto chvíli nejsou studentům dostupné žádné semestry.",
8987
"app.groupsStudent.notStudent": "Tato stránka je dostupná pouze studentům.",
@@ -92,6 +90,8 @@
9290
"app.groupsSupervisor.addAttributeModal.title": "Přidat atribut ke skupině",
9391
"app.groupsSupervisor.currentlyManagedGroups": "Skupiny",
9492
"app.groupsSupervisor.notSuperadmin": "Tato stránka je k dispozici pouze administrátorům ReCodExu.",
93+
"app.groupsSupervisor.plantTermButton": "Osadit skupiny pro",
94+
"app.groupsSupervisor.plantTermGroupsModal.title": "Osadit skupiny pro semestr",
9595
"app.groupsSupervisor.title": "Spravovat všechny skupiny a jejich vazby",
9696
"app.groupsTeacher.aboutStudentsInfo": "Studenti nejsou do skupiny přidáni automaticky, ale mohou se do skupiny připojit sami prostřednictvím rozšíření SIS-CodEx.",
9797
"app.groupsTeacher.bindGroupInfo": "Vybraný událost bude svázána s existující cílovou skupinou, kterou můžete vybrat níže.",
@@ -198,6 +198,8 @@
198198
"app.terms.form.year.explanation": "Kalendářní rok, ve kterém akademický rok začíná (např. 2025 pro akademický rok 2025/26).",
199199
"app.terms.noTerms": "V tuto chvíli nejsou vytvořeny žádné semestry.",
200200
"app.terms.refresh": "Občerstvit semestry",
201+
"app.terms.summer": "Léto",
202+
"app.terms.summerLong": "Letní semestr",
201203
"app.terms.table.Archive after": "Archivovat po",
202204
"app.terms.table.beginning": "Začátek",
203205
"app.terms.table.deleteButton": "Smazat",
@@ -206,13 +208,13 @@
206208
"app.terms.table.end": "Konec",
207209
"app.terms.table.studentsFrom": "Studenti od",
208210
"app.terms.table.studentsUntil": "Studenti do",
209-
"app.terms.table.summer": "Léto",
210211
"app.terms.table.teachersFrom": "Učitelé od",
211212
"app.terms.table.teachersUntil": "Učitelé do",
212213
"app.terms.table.term": "Semestr",
213-
"app.terms.table.winter": "Zima",
214214
"app.terms.termsList.title": "Seznam semestrů",
215215
"app.terms.title": "Semestry",
216+
"app.terms.winter": "Zima",
217+
"app.terms.winterLong": "Zimní semestr",
216218
"app.user.diffBox.email": "Email",
217219
"app.user.diffBox.firstName": "Křestní jméno",
218220
"app.user.diffBox.lastLoaded": "Naposledy načteno",
@@ -240,4 +242,4 @@
240242
"generic.operationFailed": "Operace selhala",
241243
"generic.reset": "Resetovat",
242244
"generic.save": "Uložit"
243-
}
245+
}

0 commit comments

Comments
 (0)