Skip to content

Commit 1f1a042

Browse files
committed
feat: allow setting calendar entry colors for carers
1 parent bed152e commit 1f1a042

File tree

11 files changed

+114
-33
lines changed

11 files changed

+114
-33
lines changed

apps/backend/db/migrations/.snapshot-postgres.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@
156156
"length": 255,
157157
"mappedType": "string"
158158
},
159+
"color": {
160+
"name": "color",
161+
"type": "varchar(255)",
162+
"unsigned": false,
163+
"autoincrement": false,
164+
"primary": false,
165+
"nullable": true,
166+
"length": 255,
167+
"mappedType": "string"
168+
},
159169
"participant_id": {
160170
"name": "participant_id",
161171
"type": "bigint",
@@ -190,7 +200,14 @@
190200
"unique": true
191201
}
192202
],
193-
"checks": [],
203+
"checks": [
204+
{
205+
"name": "carer_color_check",
206+
"expression": "color ~* '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'",
207+
"definition": "check ((color ~* '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'))",
208+
"columnName": "color"
209+
}
210+
],
194211
"foreignKeys": {
195212
"carer_participant_id_foreign": {
196213
"constraintName": "carer_participant_id_foreign",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Migration } from "@mikro-orm/migrations";
2+
3+
export class Migration20241217101643 extends Migration {
4+
override async up(): Promise<void> {
5+
this.addSql(`alter table "carer" add column "color" varchar(255) null;`);
6+
this.addSql(`alter table "carer" add constraint carer_color_check check(color ~* '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\$');`);
7+
}
8+
9+
override async down(): Promise<void> {
10+
this.addSql(`alter table "carer" drop constraint carer_color_check;`);
11+
this.addSql(`alter table "carer" drop column "color";`);
12+
}
13+
}

apps/backend/src/defaults/carers/carer.dto.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty, OmitType, PartialType } from "@nestjs/swagger";
2-
import { IsNotEmpty, MinLength } from "class-validator";
2+
import { IsNotEmpty, Matches, MinLength } from "class-validator";
33
import { Type } from "class-transformer";
44
import { ParticipantDto } from "../../research/participants/participant.dto";
55

@@ -12,6 +12,10 @@ export class CarerDto {
1212
@IsNotEmpty()
1313
name: string;
1414

15+
@Matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
16+
@ApiProperty({ example: "#ffffff", description: "The color used to display entries in the calendar" })
17+
color?: string;
18+
1519
@Type(() => ParticipantDto)
1620
participant?: ParticipantDto;
1721

apps/backend/src/defaults/carers/carer.entity.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Collection, Entity, ManyToOne, OneToMany, Property } from "@mikro-orm/core";
1+
import { Check, Collection, Entity, ManyToOne, OneToMany, Property } from "@mikro-orm/core";
22
import { BaseEntity } from "../../common/entities/base.entity";
33
import { Participant } from "../../research/participants/participant.entity";
44
import { Entry } from "../../research/entries/entry.entity";
@@ -8,6 +8,10 @@ export class Carer extends BaseEntity {
88
@Property({ unique: true })
99
name!: string;
1010

11+
@Check<Carer>({ expression: (columns) => `${columns.color} ~* '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'` })
12+
@Property()
13+
color?: string;
14+
1115
@ManyToOne()
1216
participant?: Participant;
1317

apps/frontend/src/api.gen.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,11 @@ export interface components {
477477
* @example Grandmother
478478
*/
479479
name: string;
480+
/**
481+
* @description The color used to display entries in the calendar
482+
* @example #ffffff
483+
*/
484+
color?: string;
480485
participant?: number;
481486
};
482487
StudyDto: {
@@ -551,6 +556,11 @@ export interface components {
551556
* @example Grandmother
552557
*/
553558
name: string;
559+
/**
560+
* @description The color used to display entries in the calendar
561+
* @example #ffffff
562+
*/
563+
color?: string;
554564
participant?: components["schemas"]["ParticipantDto"];
555565
entries: number[];
556566
};
@@ -560,6 +570,11 @@ export interface components {
560570
* @example Grandmother
561571
*/
562572
name?: string;
573+
/**
574+
* @description The color used to display entries in the calendar
575+
* @example #ffffff
576+
*/
577+
color?: string;
563578
participant?: number;
564579
};
565580
LanguageCreationDto: {
@@ -729,6 +744,11 @@ export interface components {
729744
* @example Grandmother
730745
*/
731746
name: string;
747+
/**
748+
* @description The color used to display entries in the calendar
749+
* @example #ffffff
750+
*/
751+
color?: string;
732752
participant?: components["schemas"]["ParticipantDto"];
733753
entries: number[];
734754
};

apps/frontend/src/components/CarerForm.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, TextInput, useForm } from "@quassel/ui";
1+
import { Button, ColorInput, TextInput, useForm, uzhColors } from "@quassel/ui";
22
import { useEffect } from "react";
33

44
type CarerFormProps = {
@@ -9,6 +9,7 @@ type CarerFormProps = {
99

1010
type FormValues = {
1111
name: string;
12+
color?: string;
1213
};
1314

1415
export function CarerForm({ carer, onSave, loading }: CarerFormProps) {
@@ -19,13 +20,15 @@ export function CarerForm({ carer, onSave, loading }: CarerFormProps) {
1920
});
2021

2122
useEffect(() => {
22-
if (carer) f.initialize(carer);
23+
if (carer) f.initialize({ ...carer, color: carer.color ?? "" });
2324
}, [carer]);
2425

2526
return (
2627
<form autoComplete="off" onSubmit={f.onSubmit(onSave)}>
2728
<TextInput label="Name" type="name" {...f.getInputProps("name")} />
2829

30+
<ColorInput label="Color" {...f.getInputProps("color")} swatchesPerRow={6} swatches={Object.values(uzhColors).flat()} />
31+
2932
<Button type="submit" fullWidth mt="xl" loading={loading}>
3033
Change
3134
</Button>

apps/frontend/src/routes/_auth/administration/carers/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createFileRoute, Link } from "@tanstack/react-router";
22
import { $api } from "../../../../stores/api";
3-
import { Button, Table } from "@quassel/ui";
3+
import { Button, ColorSwatch, Table } from "@quassel/ui";
44

55
function AdministrationCarersIndex() {
66
const carers = $api.useSuspenseQuery("get", "/carers");
@@ -18,13 +18,15 @@ function AdministrationCarersIndex() {
1818
<Table.Tr>
1919
<Table.Th>Id</Table.Th>
2020
<Table.Th>Name</Table.Th>
21+
<Table.Th>Color</Table.Th>
2122
</Table.Tr>
2223
</Table.Thead>
2324
<Table.Tbody>
2425
{carers.data?.map((c) => (
2526
<Table.Tr key={c.id}>
2627
<Table.Td>{c.id}</Table.Td>
2728
<Table.Td>{c.name}</Table.Td>
29+
<Table.Td>{c.color && <ColorSwatch color={c.color} />}</Table.Td>
2830
<Table.Td>
2931
<Button variant="default" renderRoot={(props) => <Link to={`/administration/carers/edit/${c.id}`} {...props} />}>
3032
Edit

apps/frontend/src/routes/_auth/questionnaire/_questionnaire/$id/entries.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ function QuestionnaireEntries() {
9393
end: getDateFromTimeAndWeekday(endedAt, weekday),
9494
title: carer.name,
9595
extendedProps: { entryLanguages, weeklyRecurring },
96-
backgroundColor: theme.colors[theme.primaryColor][4],
96+
backgroundColor: carer.color ?? theme.colors[theme.primaryColor][4],
97+
borderColor: carer.color ?? theme.colors[theme.primaryColor][4],
9798
})) ?? [];
9899

99100
const reset = () => {

libs/ui/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export {
5454
Checkbox,
5555
Combobox,
5656
Container,
57+
ColorInput,
58+
ColorSwatch,
5759
Divider,
5860
Flex,
5961
Group,
@@ -102,3 +104,5 @@ export {
102104

103105
export { DSVImport } from "react-dsv-import";
104106
export type { ColumnType } from "react-dsv-import";
107+
108+
export { uzhColors } from "./theme/uzh";

libs/ui/src/theme/ThemeProvider.tsx

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,13 @@ import customParseFormat from "dayjs/plugin/customParseFormat";
77
import de from "dayjs/locale/de";
88
import { DefaultMantineColor, MantineColorsTuple } from "@mantine/core";
99
import { Notifications } from "@mantine/notifications";
10+
import { convertUZHColorsToMantine, UZHColor, uzhColors } from "./uzh";
1011

1112
dayjs.extend(utc);
1213
dayjs.extend(customParseFormat);
1314
dayjs.locale(de);
1415

15-
type ExtendedCustomColors =
16-
| "uzhBlue"
17-
| "uzhCyan"
18-
| "uzhGreen"
19-
| "uzhGold"
20-
| "uzhOrange"
21-
| "uzhBerry"
22-
| "uzhBlack"
23-
| "uzhWhite"
24-
| DefaultMantineColor;
16+
type ExtendedCustomColors = UZHColor | DefaultMantineColor;
2517

2618
declare module "@mantine/core" {
2719
export interface MantineThemeColorsOverride {
@@ -32,23 +24,15 @@ declare module "@mantine/core" {
3224
type ThemeProviderProps = MantineProviderProps;
3325

3426
export const theme: MantineThemeOverride = {
35-
// based on https://www.cd.uzh.ch/dam/jcr:ceea0afd-0f51-442c-b4ca-c49bb6bd1220/uzh-corporate-colors-rgb.pdf
3627
colors: {
37-
uzhBlue: ["#BACBFF", "#7596FF", "#3062FF", "#3062FF", "#0028A5", "#0028A5", "#0028A5", "#001E7C", "#001452", "#001452"],
38-
// https://mantine.dev/colors-generator/?color=4AC9E3
39-
uzhCyan: ["#e0fdff", "#cff4fc", "#a5e6f3", "#76d7eb", "#51cbe4", "#37c3e0", "#21c0df", "#01a9c6", "#0096b2", "#00839d"],
40-
// https://mantine.dev/colors-generator/?color=A5D233
41-
uzhGreen: ["#f6fde6", "#edf7d5", "#dbedac", "#c7e380", "#b7da5b", "#acd543", "#a6d336", "#90ba27", "#7fa51e", "#6c8f0f"],
42-
// https://mantine.dev/colors-generator/?color=FFC845
43-
uzhGold: ["#fff9df", "#fff1ca", "#ffe299", "#ffd163", "#ffc336", "#ffbb18", "#ffb602", "#e4a000", "#cb8d00", "#b07900"],
44-
// https://mantine.dev/colors-generator/?color=FC4C02
45-
uzhOrange: ["#ffeee3", "#ffddcd", "#ffb99b", "#fe9366", "#fd7238", "#fd5e1b", "#fd530b", "#e24300", "#ca3a00", "#b02e00"],
46-
// https://mantine.dev/colors-generator/?color=BF0D3E
47-
uzhBerry: ["#ffeaf1", "#fcd3de", "#f6a3ba", "#f27194", "#ee4874", "#ed2f5f", "#ed2255", "#d31646", "#bd0d3d", "#a60033"],
48-
// https://mantine.dev/colors-generator/?color=000000
49-
uzhBlack: ["#f5f5f5", "#e7e7e7", "#cdcdcd", "#b2b2b2", "#9a9a9a", "#8b8b8b", "#848484", "#717171", "#656565", "#575757"],
50-
// https://mantine.dev/colors-generator/?color=FFFFFF
51-
uzhWhite: ["#f5f5f5", "#e7e7e7", "#cdcdcd", "#b2b2b2", "#9a9a9a", "#8b8b8b", "#848484", "#717171", "#656565", "#575757"],
28+
uzhBlue: convertUZHColorsToMantine(uzhColors.uzhBlue),
29+
uzhCyan: convertUZHColorsToMantine(uzhColors.uzhCyan),
30+
uzhGreen: convertUZHColorsToMantine(uzhColors.uzhGreen),
31+
uzhGold: convertUZHColorsToMantine(uzhColors.uzhGold),
32+
uzhOrange: convertUZHColorsToMantine(uzhColors.uzhOrange),
33+
uzhBerry: convertUZHColorsToMantine(uzhColors.uzhBerry),
34+
uzhBlack: convertUZHColorsToMantine(uzhColors.uzhBlack),
35+
uzhWhite: convertUZHColorsToMantine(uzhColors.uzhWhite),
5236
},
5337
primaryColor: "uzhBlue",
5438
};

0 commit comments

Comments
 (0)