Skip to content

Multiple Reference Timelines: Timeline Designer, Manage Users extensions #5631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cc6555e
style(coursesettingform): fix course start time label typo
purfectliterature Jan 15, 2023
53a4521
feat(formdatetimepickerfield): supports `required` prop
purfectliterature Jan 15, 2023
2710845
feat(formdatetimepickerfield): can not always check format errors
purfectliterature Jan 15, 2023
0fcd72b
feat(prompt): `onClick` now forwards native event
purfectliterature Jan 15, 2023
14cb77c
feat(prompt): add event handler when prompt has fully closed
purfectliterature Jan 15, 2023
e2b0a30
feat(textfield): add event handlers when press enter and esc keys
purfectliterature Jan 15, 2023
e13ca56
feat(preload): support `void` promises
purfectliterature Jan 15, 2023
b1a45d0
feat(preload): support direct reactnode as `children`
purfectliterature Jan 15, 2023
6e4400e
feat(checkbox): support custom typography variant for description
purfectliterature Jan 15, 2023
f14cd03
feat(form): support custom `className`s
purfectliterature Jan 15, 2023
8cc575f
chore: remove redundant reselect
purfectliterature Jan 15, 2023
dea50d0
feat(reference-timelines): add react timeline designer
purfectliterature Jan 15, 2023
8233429
fix(timelinedesigner): calendar scrolls to epoch when no timelines
purfectliterature Jan 15, 2023
a95ac78
feat(manageuserstable): add filter by group, bulk assign to timeline
purfectliterature Jan 15, 2023
96bf16d
chore(eslint): disable `no-restricted-exports`
purfectliterature Jan 16, 2023
5578812
style(operation): generic type defaults to `void`
purfectliterature Jan 17, 2023
0dcb7b4
style(timelinedesigner): use global store conventions
purfectliterature Jan 17, 2023
eb7ccd2
refactor(store): add typed useappdispatch, useappselector
purfectliterature Jan 17, 2023
d734429
feat(components): searchbar -> searchfield
purfectliterature Jan 17, 2023
60ee844
fix(timelinedesigner): remove unnecessary scrollbars
purfectliterature Jan 18, 2023
4b41858
feat(timelinedesigner): remove space between items
purfectliterature Jan 18, 2023
f30d740
feat(reference_times_controller): disallow update `:lesson_plan_item`
purfectliterature Jan 18, 2023
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
6 changes: 6 additions & 0 deletions app/assets/stylesheets/course/reference_timelines.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// TODO: Remove once sidebar is fixed, and set Timeline Designer height
// to fill viewport until bottom of viewport.
// 126px is the height of the topbar + breadcrumb + margins in between.
#course-timeline-designer {
height: calc(100vh - 126px);
}
14 changes: 7 additions & 7 deletions app/controllers/course/reference_times_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create
end

def update
if @reference_time.update(reference_time_params)
if @reference_time.update(update_params)
head :ok
else
render json: { errors: @reference_time.errors.full_messages.to_sentence }, status: :bad_request
Expand All @@ -26,13 +26,13 @@ def destroy
end
end

def self.base_params
[:lesson_plan_item_id, :start_at, :bonus_end_at, :end_at]
end

private

def reference_time_params
params.require(:reference_time).permit(*self.class.base_params)
def create_params
params.require(:reference_time).permit([:lesson_plan_item_id, :start_at, :bonus_end_at, :end_at])
end

def update_params
params.require(:reference_time).permit([:start_at, :bonus_end_at, :end_at])
end
end
1 change: 1 addition & 0 deletions client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ module.exports = {
'no-underscore-dangle': 'off',
'object-curly-newline': ['error', { consistent: true }],
'prefer-destructuring': 'off',
'no-restricted-exports': 'off',
},
globals: {
window: true,
Expand Down
61 changes: 61 additions & 0 deletions client/app/api/course/ReferenceTimelines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { AxiosResponse } from 'axios';
import {
TimeData,
TimelineData,
TimelinePostData,
TimelinesData,
TimePostData,
} from 'types/course/referenceTimelines';

import BaseCourseAPI from './Base';

type Response<Result = void> = Promise<AxiosResponse<Result>>;

export default class ReferenceTimelinesAPI extends BaseCourseAPI {
_getUrlPrefix(id?: TimelineData['id']): string {
return `/courses/${this.getCourseId()}/timelines${id ? `/${id}` : ''}`;
}

index(): Response<TimelinesData> {
return this.getClient().get(this._getUrlPrefix());
}

create(data: TimelinePostData): Response<TimelineData> {
return this.getClient().post(this._getUrlPrefix(), data);
}

delete(
id: TimelineData['id'],
alternativeTimelineId?: TimelineData['id'],
): Response {
return this.getClient().delete(`${this._getUrlPrefix(id)}`, {
params: { revert_to: alternativeTimelineId },
});
}

update(id: TimelineData['id'], data: TimelinePostData): Response {
return this.getClient().patch(`${this._getUrlPrefix(id)}`, data);
}

createTime(
id: TimelineData['id'],
data: TimePostData,
): Response<{ id: TimeData['id'] }> {
return this.getClient().post(`${this._getUrlPrefix(id)}/times`, data);
}

deleteTime(id: TimelineData['id'], timeId: TimeData['id']): Response {
return this.getClient().delete(`${this._getUrlPrefix(id)}/times/${timeId}`);
}

updateTime(
id: TimelineData['id'],
timeId: TimeData['id'],
data: TimePostData,
): Response {
return this.getClient().patch(
`${this._getUrlPrefix(id)}/times/${timeId}`,
data,
);
}
}
14 changes: 14 additions & 0 deletions client/app/api/course/Users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
StaffRole,
UpdateCourseUserPatchData,
} from 'types/course/courseUsers';
import { TimelineData } from 'types/course/referenceTimelines';

import BaseCourseAPI from './Base';

Expand Down Expand Up @@ -44,6 +45,7 @@ export default class UsersAPI extends BaseCourseAPI {
users: CourseUserListData[];
permissions: ManageCourseUsersPermissions;
manageCourseUsersData: ManageCourseUsersSharedData;
timelines?: Record<TimelineData['id'], string>;
}>
> {
return this.getClient().get(`${this._baseUrlPrefix}/students`);
Expand Down Expand Up @@ -133,4 +135,16 @@ export default class UsersAPI extends BaseCourseAPI {
params,
);
}

assignToTimeline(
ids: CourseUserBasicMiniEntity['id'][],
timelineId: TimelineData['id'],
): Promise<AxiosResponse> {
const params = { course_users: { ids, reference_timeline_id: timelineId } };

return this.getClient().patch(
`${this._baseUrlPrefix}/users/assign_timeline`,
params,
);
}
}
2 changes: 2 additions & 0 deletions client/app/api/course/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import LevelAPI from './Level';
import MaterialFoldersAPI from './MaterialFolders';
import MaterialsAPI from './Materials';
import PersonalTimesAPI from './PersonalTimes';
import ReferenceTimelinesAPI from './ReferenceTimelines';
import StatisticsAPI from './Statistics';
import SurveyAPI from './Survey';
import UserEmailSubscriptionsAPI from './UserEmailSubscriptions';
Expand Down Expand Up @@ -51,6 +52,7 @@ const CourseAPI = {
materials: new MaterialsAPI(),
materialFolders: new MaterialFoldersAPI(),
personalTimes: new PersonalTimesAPI(),
referenceTimelines: new ReferenceTimelinesAPI(),
statistics: StatisticsAPI,
submissions: new SubmissionsAPI(),
survey: SurveyAPI,
Expand Down
2 changes: 1 addition & 1 deletion client/app/bundles/announcements/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import GlobalAnnouncementsAPI from 'api/Announcements';

import * as actions from './actions';

export function indexAnnouncements(): Operation<void> {
export function indexAnnouncements(): Operation {
return async (dispatch) =>
GlobalAnnouncementsAPI.announcements.index().then((response) => {
const data = response.data;
Expand Down
12 changes: 5 additions & 7 deletions client/app/bundles/course/achievement/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const formatAttributes = (data: AchievementFormData): FormData => {
return payload;
};

export function fetchAchievements(): Operation<void> {
export function fetchAchievements(): Operation {
return async (dispatch) =>
CourseAPI.achievements.index().then((response) => {
const data = response.data;
Expand All @@ -51,9 +51,7 @@ export function loadAchievement(
);
}

export function loadAchievementCourseUsers(
achievementId: number,
): Operation<void> {
export function loadAchievementCourseUsers(achievementId: number): Operation {
return async (dispatch) =>
CourseAPI.achievements
.fetchAchievementCourseUsers(achievementId)
Expand Down Expand Up @@ -84,7 +82,7 @@ export function updateAchievement(
return async () => CourseAPI.achievements.update(achievementId, attributes);
}

export function deleteAchievement(achievementId: number): Operation<void> {
export function deleteAchievement(achievementId: number): Operation {
return async (dispatch) =>
CourseAPI.achievements.delete(achievementId).then(() => {
dispatch(actions.deleteAchievement(achievementId));
Expand All @@ -94,7 +92,7 @@ export function deleteAchievement(achievementId: number): Operation<void> {
export function awardAchievement(
achievementId: number,
data: number[],
): Operation<void> {
): Operation {
const attributes = { achievement: { course_user_ids: data } };
return async (dispatch) =>
CourseAPI.achievements
Expand All @@ -107,7 +105,7 @@ export function awardAchievement(
export function updatePublishedAchievement(
achievementId: number,
data: boolean,
): Operation<void> {
): Operation {
const attributes = { achievement: { published: data } };
return async (dispatch) =>
CourseAPI.achievements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ const CourseSettingsForm = (props: CourseSettingsFormProps): JSX.Element => {
field={field}
fieldState={fieldState}
label={t(translations.startsAt)}
required
variant="filled"
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default defineMessages({
},
startsAt: {
id: 'course.admin.CourseSettings.startsAt',
defaultMessage: 'Starts as',
defaultMessage: 'Starts at',
},
endsAt: {
id: 'course.admin.CourseSettings.endsAt',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ interface Props extends WrappedComponentProps {
updateOperation?: (
announcementId: number,
formData: AnnouncementFormData,
) => Operation<void>;
deleteOperation?: (announcementId: number) => Operation<void>;
) => Operation;
deleteOperation?: (announcementId: number) => Operation;
canSticky?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from 'types/course/announcements';
import { Operation } from 'types/store';

import SearchBar from 'lib/components/core/fields/SearchBar';
import SearchField from 'lib/components/core/fields/SearchField';
import Pagination from 'lib/components/core/layouts/Pagination';

import AnnouncementCard from './AnnouncementCard';
Expand All @@ -20,8 +20,8 @@ interface Props extends WrappedComponentProps {
updateOperation?: (
announcementId: number,
formData: AnnouncementFormData,
) => Operation<void>;
deleteOperation?: (announcementId: number) => Operation<void>;
) => Operation;
deleteOperation?: (announcementId: number) => Operation;
canSticky?: boolean;
}

Expand Down Expand Up @@ -56,21 +56,17 @@ const AnnouncementsDisplay: FC<Props> = (props) => {
setShavedAnnouncements(announcements);
}, [announcements]);

const handleSearchBarChange = (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
): void => {
if (event.target.value.trim() === '') {
const handleSearchBarChange = (rawKeyword: string): void => {
const keyword = rawKeyword.trim();

if (keyword === '') {
setShavedAnnouncements(announcements);
} else {
setShavedAnnouncements(
announcements.filter(
(announcement: AnnouncementMiniEntity) =>
announcement.title
.toLowerCase()
.includes(event.target.value.trim().toLowerCase()) ||
announcement.content
.toLowerCase()
.includes(event.target.value.trim().toLowerCase()),
announcement.title.toLowerCase().includes(keyword.toLowerCase()) ||
announcement.content.toLowerCase().includes(keyword.toLowerCase()),
),
);
}
Expand All @@ -88,12 +84,12 @@ const AnnouncementsDisplay: FC<Props> = (props) => {
xs={1}
>
<div style={{ paddingTop: 7, paddingBottom: 5 }}>
<SearchBar
onChange={handleSearchBarChange}
<SearchField
className="w-[350px]"
onChangeKeyword={handleSearchBarChange}
placeholder={intl.formatMessage(
translations.searchBarPlaceholder,
)}
width={350}
/>
</div>
</Grid>
Expand Down
10 changes: 4 additions & 6 deletions client/app/bundles/course/announcements/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const formatAttributes = (data: AnnouncementFormData): FormData => {
return payload;
};

export function fetchAnnouncements(): Operation<void> {
export function fetchAnnouncements(): Operation {
return async (dispatch) =>
CourseAPI.announcements.index().then((response) => {
const data = response.data;
Expand All @@ -39,9 +39,7 @@ export function fetchAnnouncements(): Operation<void> {
});
}

export function createAnnouncement(
formData: AnnouncementFormData,
): Operation<void> {
export function createAnnouncement(formData: AnnouncementFormData): Operation {
const attributes = formatAttributes(formData);
return async (dispatch) =>
CourseAPI.announcements.create(attributes).then((response) => {
Expand All @@ -55,7 +53,7 @@ export function createAnnouncement(
export function updateAnnouncement(
announcementId: number,
formData: AnnouncementFormData,
): Operation<void> {
): Operation {
const attributes = formatAttributes(formData);
return async (dispatch) =>
CourseAPI.announcements
Expand All @@ -65,7 +63,7 @@ export function updateAnnouncement(
});
}

export function deleteAnnouncement(accouncementId: number): Operation<void> {
export function deleteAnnouncement(accouncementId: number): Operation {
return async (dispatch) =>
CourseAPI.announcements.delete(accouncementId).then(() => {
dispatch(actions.deleteAnnouncement(accouncementId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface Props {
updateOperation: (
announcementId: number,
formData: AnnouncementFormData,
) => Operation<void>;
) => Operation;
canSticky: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import AnnouncementForm from '../../components/forms/AnnouncementForm';
interface Props {
open: boolean;
onClose: () => void;
createOperation: (formData: AnnouncementFormData) => Operation<void>;
createOperation: (formData: AnnouncementFormData) => Operation;
canSticky?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ const AssessmentForm = (props: AssessmentFormProps): JSX.Element => {
field={field}
fieldState={fieldState}
label={intl.formatMessage(t.startAt)}
required
variant="filled"
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const translations = defineMessages({
},
startAt: {
id: 'course.assessment.AssessmentForm.startAt',
defaultMessage: 'Starts at *',
defaultMessage: 'Starts at',
},
endAt: {
id: 'course.assessment.AssessmentForm.endAt',
Expand Down
Loading