From e5087e909673fedd3292d354278fb9584c0a2863 Mon Sep 17 00:00:00 2001 From: BenjaVR Date: Wed, 9 Jan 2019 02:18:21 +0100 Subject: [PATCH] Students form & students list - Includes relation to schools - Added table filters - Polished modal form UI - StudentsPage added to navigation - Ant.design npm package updated --- .firebaserc | 2 +- web/package.json | 2 +- .../educations/EducationsFormModal.tsx | 3 +- .../components/schools/SchoolsFormModal.tsx | 3 +- .../components/students/StudentsFormModal.tsx | 158 +++++++++++++++ web/src/components/students/StudentsPage.tsx | 181 ++++++++++++++++++ web/src/components/students/StudentsTable.tsx | 164 ++++++++++++++++ web/src/helpers/filters.ts | 14 ++ web/src/models/Education.ts | 4 +- web/src/models/School.ts | 4 +- web/src/models/Student.ts | 7 + web/src/routes.ts | 3 +- web/src/services/FirebaseModelMapper.ts | 16 ++ web/src/services/FirestoreServiceBase.ts | 57 +++--- web/src/services/StudentsService.ts | 7 + yarn.lock | 113 +++++------ 16 files changed, 642 insertions(+), 96 deletions(-) create mode 100644 web/src/components/students/StudentsFormModal.tsx create mode 100644 web/src/components/students/StudentsPage.tsx create mode 100644 web/src/components/students/StudentsTable.tsx create mode 100644 web/src/helpers/filters.ts create mode 100644 web/src/models/Student.ts create mode 100644 web/src/services/FirebaseModelMapper.ts create mode 100644 web/src/services/StudentsService.ts diff --git a/.firebaserc b/.firebaserc index 4d945bc..b512cb2 100644 --- a/.firebaserc +++ b/.firebaserc @@ -2,4 +2,4 @@ "projects": { "default": "stud-plan" } -} \ No newline at end of file +} diff --git a/web/package.json b/web/package.json index 6c4b69c..98eed8a 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "dependencies": { - "antd": "3.11.4", + "antd": "3.12.1", "classnames": "2.2.6", "core-js": "2.6.1", "firebase": "5.7.1", diff --git a/web/src/components/educations/EducationsFormModal.tsx b/web/src/components/educations/EducationsFormModal.tsx index cbdf9f7..caf5541 100644 --- a/web/src/components/educations/EducationsFormModal.tsx +++ b/web/src/components/educations/EducationsFormModal.tsx @@ -60,7 +60,7 @@ class EducationFormModal extends React.Component
- + {getFieldDecorator("name", { validateTrigger: this.state.formValidateTrigger, rules: [ @@ -69,7 +69,6 @@ class EducationFormModal extends React.Component, )} diff --git a/web/src/components/schools/SchoolsFormModal.tsx b/web/src/components/schools/SchoolsFormModal.tsx index bd80e01..89a1bab 100644 --- a/web/src/components/schools/SchoolsFormModal.tsx +++ b/web/src/components/schools/SchoolsFormModal.tsx @@ -60,7 +60,7 @@ class SchoolsFormModal extends React.Component - + {getFieldDecorator("name", { validateTrigger: this.state.formValidateTrigger, rules: [ @@ -69,7 +69,6 @@ class SchoolsFormModal extends React.Component, )} diff --git a/web/src/components/students/StudentsFormModal.tsx b/web/src/components/students/StudentsFormModal.tsx new file mode 100644 index 0000000..99ad60b --- /dev/null +++ b/web/src/components/students/StudentsFormModal.tsx @@ -0,0 +1,158 @@ +import { Form, Input, Modal, Select } from "antd"; +import { FormComponentProps } from "antd/lib/form"; +import FormItem from "antd/lib/form/FormItem"; +import React from "react"; +import { FormValidationTrigger } from "../../helpers/types"; +import { ISchool } from "../../models/School"; +import { IStudent } from "../../models/Student"; + +interface IStudentsFormModalProps { + title: string; + okText: string; + isVisible: boolean; + studentToEdit: IStudent | undefined; + schools: ISchool[]; + isLoadingSchools: boolean; + onCloseRequest: () => void; + submitStudent(student: IStudent): Promise; +} + +type StudentFormModalProps = IStudentsFormModalProps & FormComponentProps; + +interface IStudentsFormModalState { + isSubmitting: boolean; + formValidateTrigger: FormValidationTrigger; +} + +class StudentsFormModal extends React.Component { + + constructor(props: StudentFormModalProps) { + super(props); + + this.state = { + isSubmitting: false, + formValidateTrigger: "", + }; + + this.doClose = this.doClose.bind(this); + this.handleOk = this.handleOk.bind(this); + } + + public componentDidUpdate(prevProps: StudentFormModalProps): void { + // Populate the form if it is just opened and if a student to edit is passed. + if (this.props.studentToEdit !== undefined + && this.props.isVisible === true + && prevProps.isVisible === false) { + + let schoolId; + const studentSchoolId = this.props.studentToEdit.schoolId; + if (studentSchoolId !== undefined) { + schoolId = this.props.schools.some((s) => s.id === studentSchoolId) + ? studentSchoolId + : undefined; + } + + this.props.form.setFieldsValue({ // TODO: fill these keys dynamically + firstName: this.props.studentToEdit.firstName, + lastName: this.props.studentToEdit.lastName, + schoolId, + }); + } + } + + public render(): React.ReactNode { + const { getFieldDecorator } = this.props.form; + + return ( + + + + {getFieldDecorator("firstName", { + validateTrigger: this.state.formValidateTrigger, + rules: [ + { required: true, message: "Voornaam mag niet leeg zijn" }, + ], + })( + , + )} + + + {getFieldDecorator("lastName", { + validateTrigger: this.state.formValidateTrigger, + })( + , + )} + + + {getFieldDecorator("schoolId", { + validateTrigger: this.state.formValidateTrigger, + })( + , + )} + + + + ); + } + + private renderSchoolSelectOption(school: ISchool): React.ReactNode { + return ( + {school.name} + ); + } + + private doClose(): void { + this.props.onCloseRequest(); + } + + private handleOk(): void { + // Do real-time validation (while typing) only after the first submit. + this.setState({ + formValidateTrigger: "onChange", + }); + + const fields: Array = ["firstName", "lastName", "schoolId"]; + this.props.form.validateFieldsAndScroll(fields, (errors, values) => { + if (!errors) { + this.setState({ + isSubmitting: true, + }); + + const student: IStudent = { + ...values, + }; + this.props.submitStudent(student) + .then(() => { + this.doClose(); + }) + .finally(() => { + this.setState({ + isSubmitting: false, + }); + }); + } + }); + } +} + +const WrappedStudentsFormModal = Form.create()(StudentsFormModal); +export default WrappedStudentsFormModal; diff --git a/web/src/components/students/StudentsPage.tsx b/web/src/components/students/StudentsPage.tsx new file mode 100644 index 0000000..dbbdf19 --- /dev/null +++ b/web/src/components/students/StudentsPage.tsx @@ -0,0 +1,181 @@ +import { Col, notification, Row } from "antd"; +import React from "react"; +import { ISchool } from "../../models/School"; +import { IStudent } from "../../models/Student"; +import { RoutePageComponentProps, routes } from "../../routes"; +import { SchoolsService } from "../../services/SchoolsService"; +import { StudentsService } from "../../services/StudentsService"; +import AuthenticatedLayout from "../layouts/AuthenticatedLayout"; +import StudentFormModal from "./StudentsFormModal"; +import StudentsTable from "./StudentsTable"; + +type StudentsPageProps = RoutePageComponentProps; + +interface IStudentsPageState { + students: IStudent[]; + isFetching: boolean; + isAddStudentsModalVisible: boolean; + isEditStudentsModalVisible: boolean; + studentToEdit: IStudent | undefined; + schools: ISchool[]; + isFetchingSchools: boolean; +} + +export default class StudentsPage extends React.Component { + + private readonly studentsService = new StudentsService(); + private readonly schoolsService = new SchoolsService(); + + constructor(props: StudentsPageProps) { + super(props); + + this.state = { + students: [], + isFetching: true, + isAddStudentsModalVisible: false, + isEditStudentsModalVisible: false, + studentToEdit: undefined, + schools: [], + isFetchingSchools: true, + }; + + this.openAddStudentModal = this.openAddStudentModal.bind(this); + this.closeAddStudentModal = this.closeAddStudentModal.bind(this); + this.openEditStudentModal = this.openEditStudentModal.bind(this); + this.closeEditStudentModal = this.closeEditStudentModal.bind(this); + this.addStudent = this.addStudent.bind(this); + this.editStudent = this.editStudent.bind(this); + this.deleteStudent = this.deleteStudent.bind(this); + } + + public componentDidMount(): void { + this.studentsService.subscribe((students) => { + this.setState({ + isFetching: false, + students, + }); + }); + this.schoolsService.subscribe((schools) => { + this.setState({ + isFetchingSchools: false, + schools, + }); + }); + } + + public render(): React.ReactNode { + return ( + + + + + + + + + + + + ); + } + + private openAddStudentModal(): void { + this.setState({ isAddStudentsModalVisible: true }); + } + + private closeAddStudentModal(): void { + this.setState({ isAddStudentsModalVisible: false }); + } + + private openEditStudentModal(student: IStudent): void { + this.setState({ + isEditStudentsModalVisible: true, + studentToEdit: student, + }); + } + + private closeEditStudentModal(): void { + this.setState({ isEditStudentsModalVisible: false }); + } + + private addStudent(student: IStudent): Promise { + return new Promise((resolve, reject): void => { + this.studentsService.add(student) + .then(() => { + notification.success({ + message: `Student "${student.firstName}" succesvol toegevoegd`, + }); + resolve(); + }) + .catch((error) => { + notification.error({ + message: "Kon student niet toevoegen", + }); + reject(error); + }); + }); + } + + private editStudent(student: IStudent): Promise { + student.id = this.state.studentToEdit!.id; + return new Promise((resolve, reject): void => { + this.studentsService.update(student) + .then(() => { + notification.success({ + message: `Student "${student.firstName}" succesvol bewerkt`, + }); + resolve(); + }) + .catch((error) => { + notification.error({ + message: "Kon student niet bewerken", + }); + reject(error); + }); + }); + } + + private deleteStudent(student: IStudent): Promise { + return new Promise((resolve, reject): void => { + this.studentsService.delete(student) + .then(() => { + notification.success({ + message: `Student "${student.firstName}" succesvol verwijderd`, + }); + resolve(); + }) + .catch((error) => { + notification.error({ + message: `Kon student "${student.firstName}" niet verwijderen, probeer later opnieuw`, + }); + reject(error); + }); + }); + } +} diff --git a/web/src/components/students/StudentsTable.tsx b/web/src/components/students/StudentsTable.tsx new file mode 100644 index 0000000..d32c6ca --- /dev/null +++ b/web/src/components/students/StudentsTable.tsx @@ -0,0 +1,164 @@ +import { Button, Col, Popconfirm, Row, Spin, Table, Tooltip } from "antd"; +import { ColumnFilterItem, ColumnProps } from "antd/lib/table"; +import React from "react"; +import { emptyFilterOptionValue, exactMatchOrDefaultOptionFilter, hasElementWithId } from "../../helpers/filters"; +import { stringSorter } from "../../helpers/sorters"; +import { ISchool } from "../../models/School"; +import { IStudent } from "../../models/Student"; +import styles from "../DataTable.module.scss"; + +interface IStudentsTableProps { + isLoading: boolean; + students: IStudent[]; + schools: ISchool[]; + isLoadingSchools: boolean; + deleteStudent: (student: IStudent) => Promise; + onAddStudentRequest: () => void; + onEditStudentRequest: (student: IStudent) => void; +} + +class StudentsTable extends React.Component { + + constructor(props: IStudentsTableProps) { + super(props); + + this.renderSchoolName = this.renderSchoolName.bind(this); + this.renderActions = this.renderActions.bind(this); + this.renderTableTitle = this.renderTableTitle.bind(this); + } + + public render(): React.ReactNode { + const columns = this.getTableColumns(); + + return ( + + ); + } + + private renderSchoolName(student: IStudent): React.ReactNode { + if (this.props.isLoadingSchools) { + return ; + } + + if (student.schoolId === undefined) { + return ""; + } + + return this.getSchoolNameForStudent(student); + } + + private renderActions(student: IStudent): React.ReactNode { + const deleteFunc = () => this.props.deleteStudent(student); + const editFunc = () => this.props.onEditStudentRequest(student); + return ( + + + + + + + ); + } + + private generateTableRowKey(record: IStudent, index: number): string { + return record.id || index.toString(); + } + + private getSchoolNameForStudent(student: IStudent): string { + const school = this.props.schools.find((s) => student.schoolId !== undefined && student.schoolId === s.id); + if (school === undefined) { + return ""; + } + + return school.name; + } + + private getTableColumns(): Array> { + return [ + { + title: "Naam", + key: "name", + render: (record: IStudent) => `${record.firstName} ${record.lastName}`, + sorter: (a, b) => stringSorter(`${a.firstName} ${a.lastName}`, `${b.firstName} ${b.lastName}`), + }, + { + title: "School", + key: "school", + render: (record: IStudent) => this.renderSchoolName(record), + sorter: (a, b) => stringSorter(this.getSchoolNameForStudent(a), this.getSchoolNameForStudent(b)), + filters: this.getSchoolFilters(), + onFilter: (value, record: IStudent) => { + return exactMatchOrDefaultOptionFilter(value, + hasElementWithId(this.props.schools, record.schoolId) + ? record.schoolId + : undefined); + }, + }, + { + title: "Acties", + key: "actions", + width: 120, + align: "center", + render: (record: IStudent) => this.renderActions(record), + }, + ]; + } + + private getSchoolFilters(): ColumnFilterItem[] { + const filters: ColumnFilterItem[] = this.props.schools.map((school) => { + return { + text: school.name, + value: school.id!, + }; + }); + filters.unshift({ + text: "Zonder school", + value: emptyFilterOptionValue, + }); + return filters; + } +} + +export default StudentsTable; diff --git a/web/src/helpers/filters.ts b/web/src/helpers/filters.ts new file mode 100644 index 0000000..699f9e1 --- /dev/null +++ b/web/src/helpers/filters.ts @@ -0,0 +1,14 @@ +import { IFirebaseTable } from "../services/FirestoreServiceBase"; + +export const emptyFilterOptionValue = "@@__"; + +export function exactMatchOrDefaultOptionFilter(filterValue: string, recordValue: string | undefined): boolean { + if (filterValue === emptyFilterOptionValue) { + return recordValue === undefined || recordValue === null || recordValue === ""; + } + return filterValue === recordValue; +} + +export function hasElementWithId(elements: T[], id: string | undefined): boolean { + return elements.filter((e) => e.id !== undefined).some((e) => e.id === id); +} diff --git a/web/src/models/Education.ts b/web/src/models/Education.ts index 71bfb26..537baba 100644 --- a/web/src/models/Education.ts +++ b/web/src/models/Education.ts @@ -1,5 +1,5 @@ -import { IFirebaseModel } from "../services/FirestoreServiceBase"; +import { IFirebaseTable } from "../services/FirestoreServiceBase"; -export interface IEducation extends IFirebaseModel { +export interface IEducation extends IFirebaseTable { name: string; } diff --git a/web/src/models/School.ts b/web/src/models/School.ts index 1286250..e53d04e 100644 --- a/web/src/models/School.ts +++ b/web/src/models/School.ts @@ -1,5 +1,5 @@ -import { IFirebaseModel } from "../services/FirestoreServiceBase"; +import { IFirebaseTable } from "../services/FirestoreServiceBase"; -export interface ISchool extends IFirebaseModel { +export interface ISchool extends IFirebaseTable { name: string; } diff --git a/web/src/models/Student.ts b/web/src/models/Student.ts new file mode 100644 index 0000000..5f7cf13 --- /dev/null +++ b/web/src/models/Student.ts @@ -0,0 +1,7 @@ +import { IFirebaseTable } from "../services/FirestoreServiceBase"; + +export interface IStudent extends IFirebaseTable { + firstName: string; + lastName?: string; + schoolId?: string; +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 9e66774..36991b9 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -3,6 +3,7 @@ import { RouteComponentProps } from "react-router"; import LoginPage from "./components/auth/login/LoginPage"; import EducationsPage from "./components/educations/EducationsPage"; import SchoolsPage from "./components/schools/SchoolsPage"; +import StudentsPage from "./components/students/StudentsPage"; export type RoutePageComponentProps = RouteComponentProps; @@ -27,7 +28,7 @@ const planningsRoute: IRoute = { const studentsRoute: IRoute = { title: "Studenten", url: "/students", - component: SchoolsPage, + component: StudentsPage, }; const schoolsRoute: IRoute = { diff --git a/web/src/services/FirebaseModelMapper.ts b/web/src/services/FirebaseModelMapper.ts new file mode 100644 index 0000000..fad1c95 --- /dev/null +++ b/web/src/services/FirebaseModelMapper.ts @@ -0,0 +1,16 @@ +import { IFirebaseTable } from "./FirestoreServiceBase"; + +export class FirebaseModelMapper { + + public static mapDocsToObject(docSnaps: firebase.firestore.DocumentSnapshot[]): T[] { + return docSnaps.map((docSnap) => FirebaseModelMapper.mapDocToObject(docSnap)); + } + + public static mapDocToObject(docSnap: firebase.firestore.DocumentSnapshot): T { + const obj = { + ...docSnap.data(), + id: docSnap.id, + }; + return obj as T; + } +} diff --git a/web/src/services/FirestoreServiceBase.ts b/web/src/services/FirestoreServiceBase.ts index 6f2b7ec..0f5a07c 100644 --- a/web/src/services/FirestoreServiceBase.ts +++ b/web/src/services/FirestoreServiceBase.ts @@ -1,32 +1,47 @@ import firebase from "firebase"; import { Firebase } from "../config/FirebaseInitializer"; +import { FirebaseModelMapper } from "./FirebaseModelMapper"; export type OrderByType = "asc" | "desc"; export type ListenOnChangeFunc = (objects: T[], size: number) => void; -export interface IFirebaseModel { +export interface IFirebaseTable { id?: string; createdTimestamp?: firebase.firestore.Timestamp; updatedTimestamp?: firebase.firestore.Timestamp; } -export abstract class FirestoreServiceBase { +export abstract class FirestoreServiceBase { protected readonly abstract collectionRef: firebase.firestore.CollectionReference; public subscribe(onChange: ListenOnChangeFunc, orderBy: keyof T = "updatedTimestamp", orderByType: OrderByType = "desc"): void { this.collectionRef.orderBy(orderBy as string, orderByType) .onSnapshot((change): void => { - const mappedDocs = this.mapDocsToObject(change.docs); + const mappedDocs = FirebaseModelMapper.mapDocsToObject(change.docs); onChange(mappedDocs, change.size); }); } + public get(id: string): Promise { + return new Promise((resolve, reject) => { + this.collectionRef.doc(id).get() + .then((docSnap) => { + const mappedDoc = FirebaseModelMapper.mapDocToObject(docSnap); + resolve(mappedDoc); + }) + .catch((error) => { + catchErrorDev(error); + reject(error); + }); + }); + } + public add(obj: T): Promise { - return new Promise((resolve, reject): void => { + return new Promise((resolve, reject) => { if (obj.id !== undefined) { - reject("Cannot add a object which already has an id!"); + return reject("Cannot add a object which already has an id!"); } const now = Firebase.firestore.Timestamp.now(); @@ -38,7 +53,7 @@ export abstract class FirestoreServiceBase { return docRef.get(); }) .then((doc) => { - resolve(this.mapDocToObject(doc)); + resolve(FirebaseModelMapper.mapDocToObject(doc)); }) .catch((error) => { catchErrorDev(error); @@ -48,16 +63,24 @@ export abstract class FirestoreServiceBase { } public update(obj: T): Promise { - return new Promise((resolve, reject): void => { + return new Promise((resolve, reject) => { if (obj.id === undefined) { - reject("Id is undefined"); + return reject("Id is undefined"); } const id = obj.id; delete obj.id; obj.updatedTimestamp = Firebase.firestore.Timestamp.now(); - this.collectionRef.doc(id).update(obj) + // Change all "undefined" to "delete()", because Firestore wants this. + const objToClean: { [key: string]: any } = obj; + Object.keys(objToClean).forEach((key) => { + if (objToClean[key] === undefined) { + objToClean[key] = Firebase.firestore.FieldValue.delete(); + } + }); + + this.collectionRef.doc(id).update(objToClean) .then(() => resolve()) .catch((error) => { catchErrorDev(error); @@ -67,9 +90,9 @@ export abstract class FirestoreServiceBase { } public delete(obj: T): Promise { - return new Promise((resolve, reject): void => { + return new Promise((resolve, reject) => { if (obj.id === undefined) { - reject("Id is undefined"); + return reject("Id is undefined"); } this.collectionRef.doc(obj.id).delete() @@ -80,18 +103,6 @@ export abstract class FirestoreServiceBase { }); }); } - - protected mapDocsToObject(docSnaps: firebase.firestore.DocumentSnapshot[]): T[] { - return docSnaps.map(this.mapDocToObject); - } - - protected mapDocToObject(docSnap: firebase.firestore.DocumentSnapshot): T { - const obj = { - ...docSnap.data(), - id: docSnap.id, - }; - return obj as T; - } } function catchErrorDev(error: any): void { diff --git a/web/src/services/StudentsService.ts b/web/src/services/StudentsService.ts new file mode 100644 index 0000000..6c769a4 --- /dev/null +++ b/web/src/services/StudentsService.ts @@ -0,0 +1,7 @@ +import { Firebase } from "../config/FirebaseInitializer"; +import { IStudent } from "../models/Student"; +import { FirestoreServiceBase } from "./FirestoreServiceBase"; + +export class StudentsService extends FirestoreServiceBase { + protected readonly collectionRef = Firebase.firestore().collection("students"); +} diff --git a/yarn.lock b/yarn.lock index a7bbcee..d6f7d1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ ant-design-palettes "^1.1.3" babel-runtime "^6.26.0" -"@ant-design/icons@~1.1.15": +"@ant-design/icons@~1.1.16": version "1.1.16" resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-1.1.16.tgz#ac6426216934e3f4bc108f2f48f92ed66789235e" integrity sha512-0zNVP5JYBJkfMi9HotN6QBQjF3SFmUlumJNJXZIH+pZWp/5EbrCczzlG3YTmBWoyRHAsuOGIjSFIy8v/76DTPg== @@ -1692,12 +1692,12 @@ ant-design-palettes@^1.1.3: dependencies: tinycolor2 "^1.4.1" -antd@3.11.4: - version "3.11.4" - resolved "https://registry.yarnpkg.com/antd/-/antd-3.11.4.tgz#39c4a0e9ff1a944da4bdcb23c91432f0e8729158" - integrity sha512-no4tx3A3RyoccGE9DbuR5J4pZwW1hhWM1CoVvIqEdzVkc0pFjqht4Ad1b02kHwyYkPzXMezJOn5OwpkuejLGxA== +antd@3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/antd/-/antd-3.12.1.tgz#d1b3138ee907f7884f9eae2bfdee0b1b714bfe62" + integrity sha512-CExkSp+e1GxKYlfI6f9pTf2q+m6zuECvnBJblMiTx/sM29P2uEkJcmt1JysFGMS7MNa/HAel5SvvmB4lEEPMJw== dependencies: - "@ant-design/icons" "~1.1.15" + "@ant-design/icons" "~1.1.16" "@ant-design/icons-react" "~1.1.2" array-tree-filter "^2.1.0" babel-runtime "6.x" @@ -1707,38 +1707,37 @@ antd@3.11.4: css-animation "^1.5.0" dom-closest "^0.2.0" enquire.js "^2.1.6" - intersperse "^1.0.0" lodash "^4.17.11" moment "^2.22.2" omit.js "^1.0.0" prop-types "^15.6.2" raf "^3.4.0" rc-animate "^2.5.4" - rc-calendar "~9.8.0" - rc-cascader "~0.16.0" + rc-calendar "~9.10.3" + rc-cascader "~0.17.0" rc-checkbox "~2.1.5" rc-collapse "~1.10.0" - rc-dialog "~7.2.1" + rc-dialog "~7.3.0" rc-drawer "~1.7.6" - rc-dropdown "~2.2.1" + rc-dropdown "~2.4.1" rc-editor-mention "^1.1.7" rc-form "^2.4.0" rc-input-number "~4.3.7" rc-menu "~7.4.12" rc-notification "~3.3.0" - rc-pagination "~1.17.5" + rc-pagination "~1.17.7" rc-progress "~2.2.6" - rc-rate "~2.4.2" + rc-rate "~2.5.0" rc-select "^8.6.7" rc-slider "~8.6.3" rc-steps "~3.3.0" rc-switch "~1.8.0" rc-table "~6.4.0" rc-tabs "~9.5.2" - rc-time-picker "~3.4.0" + rc-time-picker "~3.5.0" rc-tooltip "~3.7.3" rc-tree "~1.14.6" - rc-tree-select "~2.4.0" + rc-tree-select "~2.5.0" rc-trigger "^2.6.2" rc-upload "~2.6.0" rc-util "^4.5.1" @@ -1857,11 +1856,6 @@ array-reduce@~0.0.0: resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= -array-tree-filter@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-1.0.1.tgz#0a8ad1eefd38ce88858632f9cc0423d7634e4d5d" - integrity sha1-CorR7v04zoiFhjL5zAQj12NOTV0= - array-tree-filter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" @@ -3239,7 +3233,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6.3: +create-react-class@^15.5.3, create-react-class@^15.6.3: version "15.6.3" resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== @@ -5827,11 +5821,6 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" -intersperse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intersperse/-/intersperse-1.0.0.tgz#f2561fb1cfef9f5277cc3347a22886b4351a5181" - integrity sha1-8lYfsc/vn1J3zDNHoiiGtDUaUYE= - invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -9475,28 +9464,29 @@ rc-animate@^3.0.0-rc.1, rc-animate@^3.0.0-rc.4, rc-animate@^3.0.0-rc.5: rc-util "^4.5.0" react-lifecycles-compat "^3.0.4" -rc-calendar@~9.8.0: - version "9.8.2" - resolved "https://registry.yarnpkg.com/rc-calendar/-/rc-calendar-9.8.2.tgz#ee4dd2675bfbb7b4e4459910b54ec996022b9138" - integrity sha512-EAm/YAwezC9hNfn9K8gwXcNViCdlq8ss6iiZp4h36R7gu1MZQkfAIdSNWtaK26M8DnoTjmsxrUTaGjotQpJn4Q== +rc-calendar@~9.10.3: + version "9.10.5" + resolved "https://registry.yarnpkg.com/rc-calendar/-/rc-calendar-9.10.5.tgz#7943ba07f8d225d466f02539b9e12ef98e2a607d" + integrity sha512-Gu8eLkN0qNi2hLuHpJ42Hu33rTKkTxigmvWZ6p+TUBy6zCot84Zw5fiUrD5FWURNwvrO6Na+KYY/9blfhKrC0Q== dependencies: babel-runtime "6.x" classnames "2.x" - create-react-class "^15.5.2" moment "2.x" prop-types "^15.5.8" rc-trigger "^2.2.0" rc-util "^4.1.1" + react-lifecycles-compat "^3.0.4" -rc-cascader@~0.16.0: - version "0.16.2" - resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-0.16.2.tgz#e8f44702523d88aa3f28e92e440f1de5ec384d41" - integrity sha512-XYO6nJGQu4mshFJGZDIR1f1+z4mNDEfTyp3LSZ6KhxYlnBoTuTHbuduph2J/sM1ziVjrQ2WjdUu29Rh1BuVOMQ== +rc-cascader@~0.17.0: + version "0.17.1" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-0.17.1.tgz#914481c3370b5fd8f82e4f9df9b6596dfeda14d5" + integrity sha512-JED1iOLpj1+uob+0Asd4zwhhMRp3gLs2iYOY2/0OsdEsPc8Qj6TUwj8+isVtqyXiwGWG3vo8XgO6KCM/i7ZFqQ== dependencies: - array-tree-filter "^1.0.0" + array-tree-filter "^2.1.0" prop-types "^15.5.8" rc-trigger "^2.2.0" rc-util "^4.0.4" + react-lifecycles-compat "^3.0.4" shallow-equal "^1.0.0" warning "^4.0.1" @@ -9520,10 +9510,10 @@ rc-collapse@~1.10.0: prop-types "^15.5.6" rc-animate "2.x" -rc-dialog@~7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-7.2.1.tgz#ac92fcffdf2a0eaa64b77f829336653d911a57be" - integrity sha512-UgKNFq6ekC7XZUnXYss/9/HSDajwy04qJ++8Iuecvfq48FbMf1k9UtTW0QGcbKIfLHWUJV13/DsTBJki2H6vWw== +rc-dialog@~7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-7.3.0.tgz#d5b8c4bb4f4b2ac38bb5a839ab9e255b8a88b1ac" + integrity sha512-YLQHqZuU0cO02LUwhCsCCtvSw24SKLrT4DkNHCNGGcH9YpZP/IOFaH4zVUmXGEQiwyt0D1f3volHthMCKzLzMg== dependencies: babel-runtime "6.x" rc-animate "2.x" @@ -9539,12 +9529,13 @@ rc-drawer@~1.7.6: prop-types "^15.5.0" rc-util "^4.5.1" -rc-dropdown@~2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-2.2.1.tgz#172b6e87f0909fe8ab983e375f62e2866f3250c3" - integrity sha512-eyTYEkdyhQGHcNc+YDH6MrExX783zPAxQCnQ0+ArIchXmKmDw6MRV88kbRT69jfmqEkH6n8kOKt65EFCNWzk6g== +rc-dropdown@~2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-2.4.1.tgz#aaef6eb3a5152cdd9982895c2a78d9b5f046cdec" + integrity sha512-p0XYn0wrOpAZ2fUGE6YJ6U8JBNc5ASijznZ6dkojdaEfQJAeZtV9KMEewhxkVlxGSbbdXe10ptjBlTEW9vEwEg== dependencies: babel-runtime "^6.26.0" + classnames "^2.2.6" prop-types "^15.5.8" rc-trigger "^2.5.1" react-lifecycles-compat "^3.0.2" @@ -9637,10 +9628,10 @@ rc-notification@~3.3.0: rc-animate "2.x" rc-util "^4.0.4" -rc-pagination@~1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-1.17.5.tgz#56bf8748dc42920f1f8c0f03cdd0685d321f0238" - integrity sha512-SHxN9P/cpoxdhKwSDWnLTTMQKTEL1D4qSUdnjdjEZcwO+RfxCnVJ4SjfFq0lsyiKfaYdD2HVYZoAiXtPdOwf9A== +rc-pagination@~1.17.7: + version "1.17.8" + resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-1.17.8.tgz#65583bebe13fffe4de7f418e1a6c86374ceabceb" + integrity sha512-duEV+K/b/nZNGr943+TMCEcY4xWkjAkpKW0Vr7fSR8wQk0DY7aTJC+k+vjl4X2EzEmPXqy85hibzpsO9vydKAw== dependencies: babel-runtime "6.x" prop-types "^15.5.7" @@ -9654,12 +9645,11 @@ rc-progress@~2.2.6: babel-runtime "6.x" prop-types "^15.5.8" -rc-rate@~2.4.2: - version "2.4.3" - resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.4.3.tgz#70434905faf84c9a0694bec4bdbd9b9a9099318f" - integrity sha512-MyE0toUYgAFpy4ONbammAkktwlPmmoOAazWWmae8iz2KaJ4Pj3itN1DgAcamfIdpyDe97drldY2l7AvCg1fUSQ== +rc-rate@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.5.0.tgz#72d4984a03d0a7a0e6779c7a79efcea27626abf6" + integrity sha512-aXX5klRqbVZxvLghcKnLqqo7LvLVCHswEDteWsm5Gb7NBIPa1YKTcAbvb5SZ4Z4i4EeRoZaPwygRAWsQgGtbKw== dependencies: - babel-runtime "^6.26.0" classnames "^2.2.5" prop-types "^15.5.8" rc-util "^4.3.0" @@ -9746,12 +9736,11 @@ rc-tabs@~9.5.2: rc-util "^4.0.4" warning "^3.0.0" -rc-time-picker@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.4.0.tgz#274e80122f885b37a4eace7393f3a25334fa141f" - integrity sha512-BgF0Fu/d36AK0h8jYBa01VWCm5vHWtYCh4DXBQhNazPLSH9hMP6JHLMJPSYMJ9jKttdE18O+F3j0mVQCL8JpDg== +rc-time-picker@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.5.0.tgz#bbe8d50a677eff1cd65cb645cb0fc29f83b91c35" + integrity sha512-swJyFZgR3P4UgFix5DP0fQQPKHP7WYEKlzbAWXd72TmFV80VCxmR+l0OWyCOjZgXfo9VJ/mEDzUnMSjP8/xyrg== dependencies: - babel-runtime "6.x" classnames "2.x" moment "2.x" prop-types "^15.5.8" @@ -9766,10 +9755,10 @@ rc-tooltip@^3.7.0, rc-tooltip@~3.7.3: prop-types "^15.5.8" rc-trigger "^2.2.2" -rc-tree-select@~2.4.0: - version "2.4.4" - resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-2.4.4.tgz#71d50f229548a0d13d3a915b25a7f82447b3fd10" - integrity sha512-jg64hqD9KRfmjhbAjraDi65rzNuiu4llIVpmmfdaoRwSbdbbBNK/SNhXAy2UQ5QmTzKqzYuRj81IJEV2wH2K/w== +rc-tree-select@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-2.5.0.tgz#826df8239d08a071993982cba0b9569db78d4098" + integrity sha512-KpmJ30+WlX/2LHpfKmAFQ8+4WR/NL5LzYltIAFzp2ZaBlVXSSLo6LnBb33kYnIU0NngVbsS5/dpsOeiHhdV0TA== dependencies: babel-runtime "^6.23.0" classnames "^2.2.1"