Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 15 additions & 15 deletions Angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, ViewChild } from '@angular/core';
import { DxDataGridComponent } from 'devextreme-angular';
import { DxDataGridComponent, DxDataGridTypes } from 'devextreme-angular/ui/data-grid';
import DataSource from 'devextreme/data/data_source';
import {
Service, Student, Subject, StudentSubject,
Expand All @@ -12,11 +12,11 @@ import {
providers: [Service],
})
export class AppComponent {
@ViewChild('mainGrid', { static: false }) mainGrid: any = DxDataGridComponent;
@ViewChild('mainGrid', { static: false }) mainGrid!: DxDataGridComponent;

title = 'Student Subjects Management';

dataSource: any;
dataSource: DataSource;

students: Student[] = [];

Expand All @@ -26,7 +26,7 @@ export class AppComponent {

popupInstance: any;

key: any;
key: string | number | null = null;

constructor(private readonly service: Service) {
this.students = service.getStudents();
Expand All @@ -39,16 +39,16 @@ export class AppComponent {
});
}

onInitNewRow(e: any): void {
onInitNewRow(e: DxDataGridTypes.InitNewRowEvent): void {
this.subjects = [];
}

onEditingStart(e: any): void {
this.subjects = [...e.data.Subjects || []];
onEditingStart(e: DxDataGridTypes.EditingStartEvent): void {
this.subjects = [...e.data.Subjects ?? []];
}

onEditorPreparing(e: any): void {
this.canBeSaved = e.row?.isNewRow || false;
onEditorPreparing(e: DxDataGridTypes.EditorPreparingEvent): void {
this.canBeSaved = e.row?.isNewRow ?? false;
this.key = e.row?.key;
}

Expand All @@ -60,13 +60,13 @@ export class AppComponent {
}
}

onSubjectEditingStart(e: any): void {
onSubjectEditingStart(e: DxDataGridTypes.EditingStartEvent): void {
if (this.popupInstance) {
this.popupInstance.option('toolbarItems[0].disabled', true);
}
}

onSubjectRowValidating(e: any): void {
onSubjectRowValidating(e: DxDataGridTypes.RowValidatingEvent): void {
if (this.popupInstance) {
if (e.isValid) {
this.popupInstance.option('toolbarItems[0].disabled', false);
Expand All @@ -76,7 +76,7 @@ export class AppComponent {
}
}

onSubjectSaved(e: any): void {
onSubjectSaved(e: DxDataGridTypes.SavedEvent): void {
this.subjects = e.component.getDataSource().items();
if (this.popupInstance) {
if (this.subjects.length > 0) {
Expand All @@ -87,7 +87,7 @@ export class AppComponent {
}
}

onSaving(e: any): void {
onSaving(e: DxDataGridTypes.SavingEvent): void {
if (e.changes[0]) {
if (e.changes[0].data) {
e.changes[0].data.Subjects = this.subjects;
Expand All @@ -101,9 +101,9 @@ export class AppComponent {
});
}

customizeText = (cellInfo: any): string => {
customizeText = (cellInfo: { value?: Subject[]; target?: string }): string => {
if (cellInfo.value && cellInfo.value.length > 0) {
return (cellInfo.value as Subject[]).reduce((text: string, subject: Subject) => `${text}${subject.Name}, `, '').slice(0, -2);
return cellInfo.value.reduce((text: string, subject: Subject) => `${text}${subject.Name}, `, '').slice(0, -2);
}
return '';
};
Expand Down
18 changes: 10 additions & 8 deletions React/src/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React, {
} from 'react';
import DataGrid, {
Column, Editing, Popup, Form, ToolbarItem, ValidationRule,
type DataGridRef,
type DataGridTypes,
} from 'devextreme-react/data-grid';
import Button from 'devextreme-react/button';
import { getStudentRows, getStudents } from '../sevices/employee';
Expand All @@ -14,25 +16,25 @@ interface EditContextShape {
setSubjectsRef: (subjects: Subject[]) => void;
saveDisabled: boolean;
setSaveDisabled: React.Dispatch<React.SetStateAction<boolean>>;
gridRef: React.MutableRefObject<any>;
gridRef: React.MutableRefObject<DataGridRef | null>;
}
const EditContext = createContext<EditContextShape | null>(null);

export function HomeComponent(): JSX.Element {
const gridRef = useRef<any>(null);
const gridRef = useRef<DataGridRef>(null);
const [data, setData] = useState<StudentRow[]>(getStudentRows());
const [students] = useState<Student[]>(getStudents());
const editingSubjectsRef = useRef<Subject[]>([]);
const [saveDisabled, setSaveDisabled] = useState(false);
const [editingKey, setEditingKey] = useState<any>(null);
const [editingKey, setEditingKey] = useState<string | number | null>(null);

const subjectsCellRender = useCallback(({ data }: { data: StudentRow }): JSX.Element => {
if (!data.Subjects || data.Subjects.length === 0) return <span />;
const text = data.Subjects.map((s: Subject) => s.Name).join(', ');
return <span>{text}</span>;
}, []);

const onSaving = useCallback((e: any) => {
const onSaving = useCallback((e: DataGridTypes.SavingEvent) => {
if (!e.changes) return;
if (e.changes[0]) {
if (e.changes[0].data) {
Expand All @@ -47,7 +49,7 @@ export function HomeComponent(): JSX.Element {
}
}, [editingKey]);

const onEditingStart = useCallback((e: any) => {
const onEditingStart = useCallback((e: DataGridTypes.EditingStartEvent) => {
setEditingKey(e.key);
const copy = e.data?.Subjects ? JSON.parse(JSON.stringify(e.data.Subjects)) : [];
editingSubjectsRef.current = copy;
Expand Down Expand Up @@ -158,7 +160,7 @@ function SubjectsEditCell(): JSX.Element | null {
const { editingSubjectsRef, setSubjectsRef, setSaveDisabled } = ctx;

const dataSourceRef = useRef<Subject[]>([...editingSubjectsRef.current]);
const gridRef = useRef<any>(null);
const gridRef = useRef<DataGridRef>(null);
const [isEditing, setIsEditing] = useState(false);

const syncToRef = useCallback((subjects: Subject[]) => {
Expand All @@ -175,13 +177,13 @@ function SubjectsEditCell(): JSX.Element | null {
setSaveDisabled(true);
}, [setSaveDisabled]);

const onRowValidating = useCallback((e: any) => {
const onRowValidating = useCallback((e: DataGridTypes.RowValidatingEvent) => {
if (!e.isValid) {
setSaveDisabled(true);
}
}, [setSaveDisabled]);

const onSaved = useCallback((e: any) => {
const onSaved = useCallback((e: DataGridTypes.SavedEvent) => {
setTimeout(() => {
const gridInstance = e.component;
const dataSource = gridInstance.option('dataSource');
Expand Down
56 changes: 38 additions & 18 deletions React/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
import applyChanges from 'devextreme/data/apply_changes';

function reducer(state: any, { type, payload }: { type: string; payload: any }): any {
let newData: any = null;
switch (type) {
case 'Saving':
if (!payload.data) payload.data = {};
interface StateShape {
data: any[];
detailData: any;
changes: any[];
editRowKey: string | number | null;
isValid?: boolean;
}

interface RowData {
data: {
[key: string]: any;
Name?: string;
Subjects?: any[];
};
type?: 'insert' | 'update' | 'remove';
isNewRow?: boolean;
}

newData = applyChanges(state.data, [payload.data], { keyExpr: payload.key });
type Action =
| { type: 'Saving'; payload: { data?: any; key?: string } }
| { type: 'Set_Changes'; payload: { changes: any[]; isValid: boolean } }
| { type: 'Set_Key'; payload: string | number | null }
| { type: 'Set_Valid'; payload: boolean };

function reducer(state: StateShape, action: Action): StateShape {
switch (action.type) {
case 'Saving': {
if (!action.payload.data) action.payload.data = {};

const newData = applyChanges(state.data, [action.payload.data], { keyExpr: action.payload.key });

return {
...state,
Expand All @@ -15,40 +38,37 @@ function reducer(state: any, { type, payload }: { type: string; payload: any }):
changes: [],
editRowKey: null,
};
}
case 'Set_Changes':

return {
...state,
changes: payload.changes,
isValid: payload.isValid,
changes: action.payload.changes,
isValid: action.payload.isValid,
};
case 'Set_Key':
return {
...state,
editRowKey: payload,
editRowKey: action.payload,
};
case 'Set_Valid':
return {
...state,
isValid: payload,
isValid: action.payload,
};
default:
return state;
}
}

function checkIsValid(row: any): boolean {
function checkIsValid(row: RowData): boolean {
let result = true;
if (row.type === 'insert' || row.isNewRow === true) {
result = row.data.Name && row.data.Name !== '' && row.data.Subjects && row.data.Subjects.length !== 0 && Object.prototype.hasOwnProperty.call(row.data, 'Name');
result = Boolean(row.data.Name && row.data.Name !== '' && row.data.Subjects && row.data.Subjects.length !== 0 && Object.prototype.hasOwnProperty.call(row.data, 'Name'));
} else if (row.type === 'update') {
if (Object.prototype.hasOwnProperty.call(row.data, 'Name')) result = row.data.Name !== '';
if (Object.prototype.hasOwnProperty.call(row.data, 'Subjects')) result = result && row.data.Subjects.length !== 0;
if (Object.prototype.hasOwnProperty.call(row.data, 'Subjects') && row.data.Subjects) result = result && row.data.Subjects.length !== 0;
}
return result;
}

export {
reducer,
checkIsValid,
};
export { reducer, checkIsValid };
33 changes: 20 additions & 13 deletions Vue/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,20 @@ import DxDataGrid, {
DxRequiredRule,
DxLookup,
DxToolbarItem,
DxItem
DxItem,
type DxDataGridTypes
} from 'devextreme-vue/data-grid';
import { students, studentSubjects, type Student, type Subject, type StudentSubject } from './data';
import SubjectsEditorComponent from './components/SubjectsEditorComponent.vue';

const dataSource = ref<StudentSubject[]>(studentSubjects);
const studentsData = ref<Student[]>(students);
const currentSubjects = ref<Subject[]>([]);
// DevExtreme popup component instance - keeping as any due to complex DX component typing
const popupInstance = ref<any>(null);
const canBeSaved = ref<boolean>(false);
const editingKey = ref<any>(null);
const editingKey = ref<string | number | null>(null);
// DevExtreme DataGrid component instance - keeping as any due to complex DX component typing
const mainGrid = ref<any>(null);

const saveButtonOptions = reactive({
Expand All @@ -116,28 +119,30 @@ const cancelButtonOptions = reactive({
onClick: () => cancelMainGrid()
});

// Use any for DevExtreme event types to avoid complex type matching
const subjectsCellTemplate = (container: HTMLElement, options: any) => {
const subjectsCellTemplate = (
container: HTMLElement,
options: { value?: Subject[]; target?: string }
) => {
if (options.value && options.value.length > 0) {
const text = options.value.map((subject: Subject) => subject.Name).join(', ');
container.textContent = text;
}
};

const onEditingStart = (e: any) => {
currentSubjects.value = [...(e.data.Subjects || [])];
const onEditingStart = (e: DxDataGridTypes.EditingStartEvent) => {
currentSubjects.value = [...(e.data.Subjects ?? [])];
};

const onEditorPreparing = (e: any) => {
canBeSaved.value = e.row.isNewRow;
editingKey.value = e.row.key;
const onEditorPreparing = (e: DxDataGridTypes.EditorPreparingEvent) => {
canBeSaved.value = e.row?.isNewRow ?? false;
editingKey.value = e.row?.key;
};

const onInitNewRow = () => {
currentSubjects.value = [];
};

const onPopupContentReady = (e: any) => {
const onPopupContentReady = (e: DxDataGridTypes.ContentReadyEvent) => {
popupInstance.value = e.component.instance();
if (canBeSaved.value) {
updateSaveButtonState(true);
Expand All @@ -153,11 +158,13 @@ const onNestedEditingStart = () => {
updateSaveButtonState(true);
};

const onNestedRowValidating = (e: any) => {
const onNestedRowValidating = (e: { isValid: boolean }) => {
updateSaveButtonState(!e.isValid);
};

const onNestedSaved = (e: any) => {
const onNestedSaved = (
e: { component: { getDataSource: () => { items: () => Subject[] } } }
) => {
currentSubjects.value = e.component.getDataSource().items();
const hasSubjects = currentSubjects.value.length > 0;
updateSaveButtonState(!hasSubjects);
Expand All @@ -177,7 +184,7 @@ const cancelMainGrid = () => {
mainGrid.value?.instance.cancelEditData();
};

const onSaving = (e: any) => {
const onSaving = (e: DxDataGridTypes.SavingEvent) => {
if (e.changes[0]) {
if (e.changes[0].data) {
e.changes[0].data.Subjects = currentSubjects.value;
Expand Down
15 changes: 8 additions & 7 deletions Vue/src/components/SubjectsEditorComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ import {
DxColumn,
DxEditing,
DxRequiredRule,
DxPatternRule
DxPatternRule,
type DxDataGridTypes
} from 'devextreme-vue/data-grid';
import type { Subject } from '../data';

/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */
interface Props {
currentSubjects: Subject[];
onSubjectsChange: (subjects: Subject[]) => void;
onEditingStart: (e: any) => void;
onEditingStart: (e: DxDataGridTypes.EditingStartEvent) => void;
onRowValidating: (e: { isValid: boolean }) => void;
onSaved: (e: { component: { getDataSource: () => { items: () => Subject[] } } }) => void;
}
Expand All @@ -69,15 +70,15 @@ const props = withDefaults(defineProps<Props>(), {
onSaved: () => () => {}
});

const handleEditingStart = (e: any) => {
const handleEditingStart = (e: DxDataGridTypes.EditingStartEvent) => {
props.onEditingStart(e);
};

const handleRowValidating = (e: { isValid: boolean }) => {
props.onRowValidating(e);
const handleRowValidating = (e: DxDataGridTypes.RowValidatingEvent) => {
props.onRowValidating({ isValid: e.isValid });
};

const handleSaved = (e: { component: { getDataSource: () => { items: () => Subject[] } } }) => {
props.onSaved(e);
const handleSaved = (e: DxDataGridTypes.SavedEvent) => {
props.onSaved({ component: e.component });
};
</script>