Skip to content

Commit

Permalink
Merge pull request #1068 from TMS-Uni-Stuttgart/Feature-user-xlsx-export
Browse files Browse the repository at this point in the history
Add feature user xlsx export
  • Loading branch information
Morphenoed authored Dec 28, 2024
2 parents 11d52d9 + 85807ef commit 2237b33
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 28 deletions.
17 changes: 17 additions & 0 deletions client/src/hooks/fetching/Files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ export async function getCredentialsPDF(): Promise<Blob> {
return Promise.reject(`Wrong response code (${response.status})`);
}

export async function getCredentialsXLSX(): Promise<Blob> {
const response = await axios.get(`/excel/credentials`, {
responseType: 'arraybuffer',
headers: {
Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
},
});

if (response.status === 200) {
return new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
}

return Promise.reject(`Wrong response code (${response.status})`);
}

export async function getTeamGradingFile(
tutorialId: string,
sheetId: string,
Expand Down
57 changes: 40 additions & 17 deletions client/src/pages/usermanagement/UserManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { Button } from '@mui/material';
import { Theme } from '@mui/material/styles';
import createStyles from '@mui/styles/createStyles';
import makeStyles from '@mui/styles/makeStyles';
import {
TableArrowDown as ImportIcon,
Printer as PrintIcon,
EmailArrowRightOutline as SendIcon,
} from 'mdi-material-ui';
import { TableArrowDown as ImportIcon, EmailArrowRightOutline as SendIcon } from 'mdi-material-ui';
import { useCallback, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { FailedMail, MailingStatus } from 'shared/model/Mail';
Expand All @@ -16,9 +12,10 @@ import { getNameOfEntity } from 'shared/util/helpers';
import UserForm, { UserFormState, UserFormSubmitCallback } from '../../components/forms/UserForm';
import LoadingSpinner from '../../components/loading/LoadingSpinner';
import SubmitButton from '../../components/loading/SubmitButton';
import SplitButton from '../../components/SplitButton';
import TableWithForm from '../../components/TableWithForm';
import { useDialog } from '../../hooks/dialog-service/DialogService';
import { getCredentialsPDF } from '../../hooks/fetching/Files';
import { getCredentialsPDF, getCredentialsXLSX } from '../../hooks/fetching/Files';
import { getAllTutorials } from '../../hooks/fetching/Tutorial';
import {
createUser,
Expand Down Expand Up @@ -113,6 +110,7 @@ function UserManagement(): JSX.Element {

const [isLoadingInitially, setLoadingInitially] = useState(true);
const [isSendingCredentials, setSendingCredentials] = useState(false);
const [isPrintingCredentials, setPrintingCredentials] = useState(false);

useEffect(() => {
if (isLoadingInitially && !isLoadingTutorials && !isLoadingUsers) {
Expand Down Expand Up @@ -319,8 +317,8 @@ function UserManagement(): JSX.Element {
});
}, [dialog, sendCredentials]);

const handlePrintCredentials = useCallback(async () => {
setSendingCredentials(true);
const handlePrintCredentialsPDF = useCallback(async () => {
setPrintingCredentials(true);

try {
const blob = await getCredentialsPDF();
Expand All @@ -330,7 +328,21 @@ function UserManagement(): JSX.Element {
enqueueSnackbar('Zugangsdaten PDF konnte nicht erzeugt werden.', { variant: 'error' });
}

setSendingCredentials(false);
setPrintingCredentials(false);
}, [enqueueSnackbar]);

const handlePrintCredentialsXLSX = useCallback(async () => {
setPrintingCredentials(true);

try {
const blob = await getCredentialsXLSX();

saveBlob(blob, 'Zugangsdaten.xlsx');
} catch {
enqueueSnackbar('Zugangsdaten XLSX konnte nicht erzeugt werden.', { variant: 'error' });
}

setPrintingCredentials(false);
}, [enqueueSnackbar]);

const sendCredentialsToSingleUser = useCallback(
Expand Down Expand Up @@ -389,15 +401,26 @@ function UserManagement(): JSX.Element {
Zugangsdaten verschicken
</SubmitButton>

<SubmitButton
variant='outlined'
isSubmitting={isSendingCredentials}
startIcon={<PrintIcon />}
onClick={handlePrintCredentials}
<SplitButton
variant='contained'
color='primary'
style={{ marginLeft: 8 }}
>
Zugangsdaten ausdrucken
</SubmitButton>
disabled={!users || users.length === 0 || isPrintingCredentials}
options={[
{
label: 'Zugangsdaten als PDF ausdrucken',
ButtonProps: {
onClick: handlePrintCredentialsPDF,
},
},
{
label: 'Zugangsdaten als XLSX ausdrucken',
ButtonProps: {
onClick: handlePrintCredentialsXLSX,
},
},
]}
/>

<Button
variant='outlined'
Expand Down
16 changes: 13 additions & 3 deletions server/src/module/excel/excel.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
ValidationPipe,
} from '@nestjs/common';
import { Response } from 'express';
import { ParseCsvResult } from 'shared/model/CSV';
import { Role } from 'shared/model/Role';
import { AllowCorrectors } from '../../guards/decorators/allowCorrectors.decorator';
import { Roles } from '../../guards/decorators/roles.decorator';
import { HasRoleGuard } from '../../guards/has-role.guard';
import { TutorialGuard } from '../../guards/tutorial.guard';
import { ParseCsvResult } from 'shared/model/CSV';
import { ParseCsvDTO } from './excel.dto';
import { ExcelService } from './excel.service';
import { Roles } from '../../guards/decorators/roles.decorator';
import { Role } from 'shared/model/Role';

@Controller('excel')
export class ExcelController {
Expand Down Expand Up @@ -53,4 +53,14 @@ export class ExcelController {
res.contentType('xlsx');
res.send(buffer);
}

@Get('/credentials')
@HttpCode(HttpStatus.OK)
@UseGuards(HasRoleGuard)
async getCredentialsXLSX(@Res() res: Response): Promise<void> {
const buffer = await this.excelService.generateCredentialsXLSX();

res.contentType('xlsx');
res.send(buffer);
}
}
11 changes: 6 additions & 5 deletions server/src/module/excel/excel.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { ExcelService } from './excel.service';
import { ExcelController } from './excel.controller';
import { TutorialModule } from '../tutorial/tutorial.module';
import { ScheincriteriaModule } from '../scheincriteria/scheincriteria.module';
import { SheetModule } from '../sheet/sheet.module';
import { StudentModule } from '../student/student.module';
import { ScheincriteriaModule } from '../scheincriteria/scheincriteria.module';
import { TutorialModule } from '../tutorial/tutorial.module';
import { UserModule } from '../user/user.module';
import { ExcelController } from './excel.controller';
import { ExcelService } from './excel.service';

@Module({
imports: [TutorialModule, SheetModule, StudentModule, ScheincriteriaModule],
imports: [TutorialModule, SheetModule, StudentModule, ScheincriteriaModule, UserModule],
providers: [ExcelService],
controllers: [ExcelController],
})
Expand Down
47 changes: 44 additions & 3 deletions server/src/module/excel/excel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { StudentStatus } from 'shared/model/Student';
import { Sheet } from '../../database/entities/sheet.entity';
import { Student } from '../../database/entities/student.entity';
import { Tutorial } from '../../database/entities/tutorial.entity';
import { ScheincriteriaService } from '../scheincriteria/scheincriteria.service';
import { SheetService } from '../sheet/sheet.service';
import { TutorialService } from '../tutorial/tutorial.service';
import { ParseCsvDTO } from './excel.dto';
import { GradingService, StudentAndGradings } from '../student/grading.service';
import { StudentService } from '../student/student.service';
import { ScheincriteriaService } from '../scheincriteria/scheincriteria.service';
import { PassedState } from '../template/template.types';
import { TutorialService } from '../tutorial/tutorial.service';
import { UserService } from '../user/user.service';
import { ParseCsvDTO } from './excel.dto';

interface HeaderData {
name: string;
Expand Down Expand Up @@ -70,6 +71,7 @@ export class ExcelService {
private readonly tutorialService: TutorialService,
private readonly sheetService: SheetService,
private readonly studentService: StudentService,
private readonly userService: UserService,
private readonly scheincriteriaService: ScheincriteriaService,
private readonly gradingService: GradingService
) {}
Expand Down Expand Up @@ -172,6 +174,45 @@ export class ExcelService {
return workbook.writeToBuffer();
}

async generateCredentialsXLSX(): Promise<Buffer> {
const users = await this.userService.findAll();
const workbook = new xl.Workbook();
const sheet = workbook.addWorksheet('User Credentials');

const headers: HeaderDataCollection<
'firstname' | 'lastname' | 'username' | 'email' | 'password'
> = {
firstname: { name: 'Firstname', column: 1 },
lastname: { name: 'Lastname', column: 2 },
username: { name: 'Username', column: 3 },
email: { name: 'Email', column: 4 },
password: { name: 'Temporary Password', column: 5 },
};
const cells: CellDataCollection<
'firstname' | 'lastname' | 'username' | 'email' | 'password'
> = {
firstname: [],
lastname: [],
username: [],
email: [],
password: [],
};

let row = 2;
for (const user of users.sort((a, b) => a.lastname.localeCompare(b.lastname))) {
cells['firstname'].push({ content: user.firstname, row });
cells['lastname'].push({ content: user.lastname, row });
cells['username'].push({ content: user.username, row });
cells['email'].push({ content: user.email || 'N/A', row });
cells['password'].push({ content: user.temporaryPassword || 'N/A', row });

row++;
}

this.fillSheet(sheet, headers, cells);
return workbook.writeToBuffer();
}

private createMemberWorksheet(workbook: Workbook, students: Student[]) {
const overviewSheet = workbook.addWorksheet('Teilnehmer');
const headers: HeaderDataCollection<MemberKeys> = {
Expand Down

0 comments on commit 2237b33

Please sign in to comment.