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
6 changes: 6 additions & 0 deletions .changeset/strong-bottles-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": minor
"@quassel/backend": minor
---

Allow creating entries for multiple days
7 changes: 3 additions & 4 deletions apps/backend/src/research/entries/entries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post } from "@nestjs/commo
import { ApiTags, ApiOperation, ApiUnprocessableEntityResponse } from "@nestjs/swagger";
import { ErrorResponseDto } from "../../common/dto/error.dto";
import { EntriesService } from "./entries.service";
import { EntryCreationDto, EntryResponseDto, EntryMutationDto } from "./entry.dto";
import { EntryCreationDto, EntryResponseDto, EntryUpdateDto } from "./entry.dto";
import { Serialize } from "../../common/decorators/serialize";

@ApiTags("Entries")
Expand All @@ -13,8 +13,7 @@ export class EntriesController {
@Post()
@ApiOperation({ summary: "Create a entry" })
@ApiUnprocessableEntityResponse({ description: "Unique name constraint violation", type: ErrorResponseDto })
@Serialize(EntryResponseDto)
create(@Body() entry: EntryCreationDto): Promise<EntryResponseDto> {
create(@Body() entry: EntryCreationDto): Promise<number[]> {
return this.entriesService.create(entry);
}

Expand All @@ -35,7 +34,7 @@ export class EntriesController {
@Patch(":id")
@ApiOperation({ summary: "Update a entry by ID" })
@Serialize(EntryResponseDto)
update(@Param("id") id: string, @Body() entry: EntryMutationDto): Promise<EntryResponseDto> {
update(@Param("id") id: string, @Body() entry: EntryUpdateDto): Promise<EntryResponseDto> {
return this.entriesService.update(+id, entry);
}

Expand Down
29 changes: 12 additions & 17 deletions apps/backend/src/research/entries/entries.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EntityRepository, UniqueConstraintViolationException, FilterQuery } from "@mikro-orm/core";
import { EntityRepository, FilterQuery } from "@mikro-orm/core";
import { InjectRepository } from "@mikro-orm/nestjs";
import { Injectable, UnprocessableEntityException } from "@nestjs/common";
import { EntryCreationDto, EntryMutationDto } from "./entry.dto";
import { Injectable } from "@nestjs/common";
import { EntryCreationDto, EntryUpdateDto } from "./entry.dto";
import { Entry } from "./entry.entity";
import { EntityManager, raw } from "@mikro-orm/postgresql";

Expand All @@ -13,20 +13,15 @@
private readonly em: EntityManager
) {}

async create(entryCreationDto: EntryCreationDto) {
const entry = new Entry();
entry.assign(entryCreationDto, { em: this.em });
create({ weekday, ...rest }: EntryCreationDto) {
return this.entryRepository.insertMany(
weekday.map((w) => {
const entry = new Entry();
entry.assign({ weekday: w, ...rest }, { em: this.em });

Check warning on line 20 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L16-L20

Added lines #L16 - L20 were not covered by tests

try {
await this.em.persist(entry).flush();
} catch (e) {
if (e instanceof UniqueConstraintViolationException) {
throw new UnprocessableEntityException("Entry with this name already exists");
}
throw e;
}

return (await entry.populate(["entryLanguages"])).toObject();
return entry;

Check warning on line 22 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L22

Added line #L22 was not covered by tests
})
);
}

async findAll() {
Expand Down Expand Up @@ -66,7 +61,7 @@
});
}

async update(id: number, entryMutationDto: EntryMutationDto) {
async update(id: number, entryMutationDto: EntryUpdateDto) {

Check warning on line 64 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L64

Added line #L64 was not covered by tests
const entry = await this.entryRepository.findOneOrFail(id, { populate: ["entryLanguages"] });

entry.assign(entryMutationDto);
Expand Down
19 changes: 11 additions & 8 deletions apps/backend/src/research/entries/entry.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ class EntryBaseDto {
@Expose()
endedAt: string;

@ApiProperty({ example: 1, description: "The weekday of the entry (Sunday is 0 like in JS)" })
@Min(0)
@Max(6)
@Expose()
weekday: number;

@ApiProperty({ example: 1, description: "The weekly recurring of the entry" })
@Min(1)
@IsOptional()
Expand All @@ -32,6 +26,12 @@ export class EntryResponseDto extends EntryBaseDto {
@Expose()
id: number;

@ApiProperty({ example: 1, description: "The weekday of the entry (Sunday is 0 like in JS)" })
@Min(0)
@Max(6)
@Expose()
weekday: number;

@Type(() => CarerResponseDto)
@Expose()
carer: CarerResponseDto;
Expand All @@ -41,14 +41,17 @@ export class EntryResponseDto extends EntryBaseDto {
entryLanguages: Array<EntryLanguageResponseDto>;
}

export class EntryCreationDto extends EntryBaseDto {
class EntryMutationDto extends EntryBaseDto {
carer: number;
questionnaire: number;

@Type(() => EntryLanguageCreationDto)
entryLanguages: Array<EntryLanguageCreationDto>;
}
export class EntryMutationDto extends PartialType(EntryCreationDto) {}
export class EntryCreationDto extends EntryMutationDto {
weekday: number[];
}
export class EntryUpdateDto extends PartialType(EntryMutationDto) {}

export class EntryTemplateDto {
@Type(() => CarerResponseDto)
Expand Down
27 changes: 9 additions & 18 deletions apps/frontend/src/api.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,16 +740,12 @@ export interface components {
* @example 2024-11-01T08:00:00.00Z
*/
endedAt: string;
/**
* @description The weekday of the entry (Sunday is 0 like in JS)
* @example 1
*/
weekday: number;
/**
* @description The weekly recurring of the entry
* @example 1
*/
weeklyRecurring?: number;
weekday: number[];
carer: number;
questionnaire: number;
entryLanguages: components["schemas"]["EntryLanguageCreationDto"][];
Expand All @@ -765,11 +761,6 @@ export interface components {
* @example 2024-11-01T08:00:00.00Z
*/
endedAt: string;
/**
* @description The weekday of the entry (Sunday is 0 like in JS)
* @example 1
*/
weekday: number;
/**
* @description The weekly recurring of the entry
* @example 1
Expand All @@ -780,10 +771,15 @@ export interface components {
* @example 1
*/
id: number;
/**
* @description The weekday of the entry (Sunday is 0 like in JS)
* @example 1
*/
weekday: number;
carer: components["schemas"]["CarerResponseDto"];
entryLanguages: components["schemas"]["EntryLanguageResponseDto"][];
};
EntryMutationDto: {
EntryUpdateDto: {
/**
* @description The starting date of the entry
* @example 2024-11-01T07:00:00.000Z
Expand All @@ -794,11 +790,6 @@ export interface components {
* @example 2024-11-01T08:00:00.00Z
*/
endedAt?: string;
/**
* @description The weekday of the entry (Sunday is 0 like in JS)
* @example 1
*/
weekday?: number;
/**
* @description The weekly recurring of the entry
* @example 1
Expand Down Expand Up @@ -1828,7 +1819,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["EntryResponseDto"];
"application/json": number[];
};
};
/** @description Unique name constraint violation */
Expand Down Expand Up @@ -1893,7 +1884,7 @@ export interface operations {
};
requestBody: {
content: {
"application/json": components["schemas"]["EntryMutationDto"];
"application/json": components["schemas"]["EntryUpdateDto"];
};
};
responses: {
Expand Down
71 changes: 43 additions & 28 deletions apps/frontend/src/components/WeekdayPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
import { SegmentedControl } from "@quassel/ui";
import { Chip, Group, Select } from "@quassel/ui";
import { i18n } from "../stores/i18n";
import { useStore } from "@nanostores/react";

type WeekdayPickerProps = {
value?: number;
onChange?: (weekday: number) => void;
};
type WeekdayPickerProps =
| { value?: number; onChange?: (weekday?: number) => void; multiple?: false }
| { value?: number[]; onChange?: (weekday: number[]) => void; multiple: true };

const messages = i18n("WeekdayPicker", {
mondayLabel: "Mo",
tusedayLabel: "Tu",
wednesdayLabel: "We",
thrusdayLabel: "Th",
fridayLabel: "Fr",
saturdayLabel: "Sa",
sundayLabel: "Su",
mondayShortLabel: "Mo",
mondayLabel: "Monday",
tusedayShortLabel: "Tu",
tusedayLabel: "Tuesday",
wednesdayShortLabel: "We",
wednesdayLabel: "Wednesday",
thursdayShortLabel: "Th",
thursdayLabel: "Thursday",
fridayShortLabel: "Fr",
fridayLabel: "Friday",
saturdayShortLabel: "Sa",
saturdayLabel: "Saturday",
sundayShortLabel: "Su",
sundayLabel: "Sunday",

Check warning on line 23 in apps/frontend/src/components/WeekdayPicker.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/WeekdayPicker.tsx#L10-L23

Added lines #L10 - L23 were not covered by tests
});

export function WeekdayPicker({ onChange, value }: WeekdayPickerProps) {
export function WeekdayPicker({ onChange, value, multiple }: WeekdayPickerProps) {

Check warning on line 26 in apps/frontend/src/components/WeekdayPicker.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/WeekdayPicker.tsx#L26

Added line #L26 was not covered by tests
const t = useStore(messages);

return (
<SegmentedControl
value={value?.toString()}
onChange={(value) => onChange?.(parseInt(value))}
data={[
{ value: "1", label: t.mondayLabel },
{ value: "2", label: t.tusedayLabel },
{ value: "3", label: t.wednesdayLabel },
{ value: "4", label: t.thrusdayLabel },
{ value: "5", label: t.fridayLabel },
{ value: "6", label: t.saturdayLabel },
{ value: "0", label: t.sundayLabel },
]}
/>
);
const weekdayOptions = [
{ value: "1", label: t.mondayLabel, short: t.mondayShortLabel },
{ value: "2", label: t.tusedayLabel, short: t.tusedayShortLabel },
{ value: "3", label: t.wednesdayLabel, short: t.wednesdayShortLabel },
{ value: "4", label: t.thursdayLabel, short: t.thursdayShortLabel },
{ value: "5", label: t.fridayLabel, short: t.fridayShortLabel },
{ value: "6", label: t.saturdayLabel, short: t.saturdayShortLabel },
{ value: "0", label: t.sundayLabel, short: t.sundayShortLabel },
];

Check warning on line 37 in apps/frontend/src/components/WeekdayPicker.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/WeekdayPicker.tsx#L29-L37

Added lines #L29 - L37 were not covered by tests

if (multiple)
return (
<Chip.Group multiple value={value?.map(String)} onChange={(values) => onChange?.(values.map(Number))}>
<Group gap={"xs"}>
{weekdayOptions.map(({ value, short }) => (
<Chip key={value} value={value}>
{short}
</Chip>
))}
</Group>
</Chip.Group>

Check warning on line 49 in apps/frontend/src/components/WeekdayPicker.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/WeekdayPicker.tsx#L39-L49

Added lines #L39 - L49 were not covered by tests
);

return <Select data={weekdayOptions} value={value?.toString()} onChange={(v) => onChange?.(v ? parseInt(v) : undefined)} />;

Check warning on line 52 in apps/frontend/src/components/WeekdayPicker.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/WeekdayPicker.tsx#L52

Added line #L52 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
const handleCreate = (entry: EntryFormValues) => {
const entryRequest = { ...entry, questionnaire: questionnaire.id };

return createMutation.mutateAsync({ body: entryRequest }, { onSuccess: reloadEntries });
return createMutation.mutateAsync({ body: entryRequest as components["schemas"]["EntryCreationDto"] }, { onSuccess: reloadEntries });

Check warning on line 65 in apps/frontend/src/components/questionnaire/QuestionnaireEntries.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/questionnaire/QuestionnaireEntries.tsx#L65

Added line #L65 was not covered by tests
};

const handleUpdate = (id: number, entry: Partial<EntryFormValues>) => {
Expand Down
Loading