Skip to content

Commit 68a7786

Browse files
committed
add crud to curriculums
1 parent 264d393 commit 68a7786

23 files changed

Lines changed: 374 additions & 122 deletions

File tree

@apps/front/app/services/store.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useLegacyStore } from '@warp-drive/legacy';
22
import { JSONAPICache } from '@warp-drive/json-api';
33
import UserSchema from '@libs/users-front/schemas/users';
4+
import CurriculumSchema from '@libs/users-front/schemas/curriculums';
45
import { setBuildURLConfig } from '@warp-drive/utilities';
56
import { CacheHandler, Fetch, RequestManager } from '@warp-drive/core';
67
import type Owner from '@ember/owner';
@@ -19,7 +20,7 @@ const legacyStore = useLegacyStore({
1920
legacyRequests: true,
2021
modelFragments: true,
2122
cache: JSONAPICache,
22-
schemas: [UserSchema],
23+
schemas: [UserSchema, CurriculumSchema],
2324
handlers: [],
2425
});
2526

@apps/front/translations/curriculums/en-us.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ moreActions:
88
duplicate: 'Duplicate'
99
download: 'Download'
1010
rename: 'Rename'
11+
validation:
12+
titleRequired: 'Title is required'
13+
minimumTitleLength: 'Title must be at least 1 character long'
14+
maximumTitleLength: 'Title must be at most 255 characters long'
15+
create:
16+
createSuccess: 'Curriculum vitae created successfully'
17+
title: 'Title'

@apps/front/translations/curriculums/fr-fr.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ moreActions:
88
duplicate: 'Dupliquer'
99
download: 'Télécharger'
1010
rename: 'Renommer'
11+
validation:
12+
titleRequired: 'Le titre est requis'
13+
minimumTitleLength: 'Le titre doit comporter au moins 1 caractère'
14+
maximumTitleLength: 'Le titre doit comporter au maximum 255 caractères'
15+
create:
16+
createSuccess: 'Le curriculum vitae a été créé avec succès'
17+
title: 'Titre'

@libs/users-front/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,10 @@
143143
"type": "addon",
144144
"main": "addon-main.cjs",
145145
"app-js": {
146-
"./components/curriculums/create/curriculum-create-form.js": "./dist/_app_/components/curriculums/create/curriculum-create-form.js",
147146
"./components/forms/login-form.js": "./dist/_app_/components/forms/login-form.js",
148147
"./components/forms/user-form.js": "./dist/_app_/components/forms/user-form.js",
149148
"./handlers/auth.js": "./dist/_app_/handlers/auth.js",
150149
"./helpers/relative-time.js": "./dist/_app_/helpers/relative-time.js",
151-
"./routes/dashboard/curriculums/create.js": "./dist/_app_/routes/dashboard/curriculums/create.js",
152150
"./routes/dashboard/curriculums/edit.js": "./dist/_app_/routes/dashboard/curriculums/edit.js",
153151
"./routes/dashboard/curriculums/index.js": "./dist/_app_/routes/dashboard/curriculums/index.js",
154152
"./routes/dashboard/users/create.js": "./dist/_app_/routes/dashboard/users/create.js",
@@ -157,10 +155,11 @@
157155
"./routes/forgot-password.js": "./dist/_app_/routes/forgot-password.js",
158156
"./routes/login.js": "./dist/_app_/routes/login.js",
159157
"./routes/logout.js": "./dist/_app_/routes/logout.js",
158+
"./schemas/curriculums.js": "./dist/_app_/schemas/curriculums.js",
160159
"./schemas/users.js": "./dist/_app_/schemas/users.js",
161160
"./services/current-user.js": "./dist/_app_/services/current-user.js",
161+
"./services/curriculum.js": "./dist/_app_/services/curriculum.js",
162162
"./services/user.js": "./dist/_app_/services/user.js",
163-
"./templates/dashboard/curriculums/create.js": "./dist/_app_/templates/dashboard/curriculums/create.js",
164163
"./templates/dashboard/curriculums/edit.js": "./dist/_app_/templates/dashboard/curriculums/edit.js",
165164
"./templates/dashboard/curriculums/index.js": "./dist/_app_/templates/dashboard/curriculums/index.js",
166165
"./templates/dashboard/users/create.js": "./dist/_app_/templates/dashboard/users/create.js",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ImmerChangeset from 'ember-immer-changeset';
2+
3+
export interface DraftCurriculum {
4+
id?: string;
5+
userId?: string;
6+
title?: string;
7+
updatedAt?: string;
8+
}
9+
10+
export class CurriculumChangeset extends ImmerChangeset<DraftCurriculum> {}

@libs/users-front/src/components/curriculums/create/curriculum-create-form.gts

Lines changed: 0 additions & 15 deletions
This file was deleted.

@libs/users-front/src/components/curriculums/curriculum-add.gts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,75 @@
11
import Component from '@glimmer/component';
22
import t from 'ember-intl/helpers/t';
3-
import { on } from '@ember/modifier';
4-
import type RouterService from '@ember/routing/router-service';
53
import { service } from '@ember/service';
4+
import TpkForm from '@triptyk/ember-input-validation/components/tpk-form';
5+
import type HandleSaveService from '@libs/shared-front/services/handle-save';
6+
import type CurriculumService from '#src/services/curriculum.ts';
7+
import ImmerChangeset from 'ember-immer-changeset';
8+
import { createCurriculumValidationSchema } from '#src/components/curriculums/curriculum-validation.ts';
9+
import type { ValidatedCurriculum } from '#src/components/curriculums/curriculum-validation.ts';
10+
import type Owner from '@ember/owner';
11+
import type { IntlService } from 'ember-intl';
612

713
interface CurriculumAddSignature {
814
Element: HTMLDivElement;
9-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
10-
Args: {};
15+
Args: {
16+
onAdd: () => void;
17+
};
1118
}
1219

1320
class CurriculumAdd extends Component<CurriculumAddSignature> {
14-
@service declare router: RouterService;
21+
@service declare handleSave: HandleSaveService;
22+
@service declare curriculum: CurriculumService;
23+
@service declare intl: IntlService;
24+
25+
changeset = new ImmerChangeset<ValidatedCurriculum>(
26+
{} as ValidatedCurriculum
27+
);
28+
validationSchema: ReturnType<typeof createCurriculumValidationSchema>;
29+
30+
constructor(owner: Owner, args: CurriculumAddSignature['Args']) {
31+
super(owner, args);
32+
this.validationSchema = createCurriculumValidationSchema(this.intl);
33+
}
34+
35+
onSubmit = async (
36+
data: ValidatedCurriculum,
37+
c: ImmerChangeset<ValidatedCurriculum>
38+
) => {
39+
await this.handleSave.handleSave({
40+
saveAction: () => this.curriculum.create(data),
41+
changeset: c,
42+
successMessage: 'curriculums.create.createSuccess',
43+
});
1544

16-
addCurriculum = () => {
17-
this.router.transitionTo('dashboard.curriculums.create');
45+
this.args.onAdd?.();
1846
};
1947

2048
<template>
21-
<button
22-
data-test-curriculum-add-button
23-
type="button"
24-
class="flex flex-col cursor-pointer items-center justify-center w-51 m-5 mb-20 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-gray-400 hover:text-gray-700 transition-colors duration-200"
25-
{{on "click" this.addCurriculum}}
49+
<TpkForm
50+
class="flex flex-col cursor-pointer items-center justify-center w-51 m-5 p-2 mb-20 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-gray-400 hover:text-gray-700 transition-colors duration-200"
51+
@changeset={{this.changeset}}
52+
@onSubmit={{this.onSubmit}}
53+
@validationSchema={{this.validationSchema}}
54+
data-test-todos-form
55+
as |F|
2656
>
27-
<span class="text-3xl font-bold">+</span>
28-
<span class="mt-2 text-sm font-medium">
29-
{{t "curriculums.view.createNewCV"}}
30-
</span>
31-
</button>
57+
<F.TpkInputPrefab
58+
@label={{t "curriculums.create.title"}}
59+
@validationField="title"
60+
class="col-span-12 m-2 md:col-span-4"
61+
/>
62+
<button
63+
data-test-curriculum-add-button
64+
type="submit"
65+
class="border-gray-300 hover:border-gray-400 rounded-lg p-4 flex flex-col items-center justify-center transition-colors duration-200 w-full mt-4"
66+
>
67+
<span class="text-3xl font-bold">+</span>
68+
<span class="mt-2 text-sm font-medium">
69+
{{t "curriculums.view.createNewCV"}}
70+
</span>
71+
</button>
72+
</TpkForm>
3273
</template>
3374
}
3475

@libs/users-front/src/components/curriculums/curriculum-item.gts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,43 @@ import {
1515
text,
1616
value,
1717
} from 'ember-cli-page-object';
18+
import type CurriculumService from '#src/services/curriculum.ts';
1819

1920
interface CurriculumItemSignature {
2021
Element: HTMLDivElement;
2122

2223
Args: {
2324
curriculum: {
24-
id: number;
25+
id: string | null;
2526
title: string;
26-
lastModified: string;
27+
updatedAt: string;
2728
};
29+
onRefresh: () => void;
2830
};
2931
}
3032

3133
class CurriculumItem extends Component<CurriculumItemSignature> {
3234
@service declare router: RouterService;
35+
@service declare curriculum: CurriculumService;
36+
37+
@action
38+
async renameOnEnter(event: KeyboardEvent) {
39+
if (event.key === 'Enter') {
40+
await this.rename((event.target as HTMLInputElement).value);
41+
}
42+
}
43+
44+
@action
45+
async renameOnBlur(event: FocusEvent) {
46+
await this.rename((event.target as HTMLInputElement).value);
47+
}
48+
49+
async rename(title: string) {
50+
if (!this.args.curriculum.id || title === this.args.curriculum.title)
51+
return;
52+
await this.curriculum.rename(this.args.curriculum.id, title);
53+
this.args.onRefresh?.();
54+
}
3355

3456
@action
3557
focusTitle() {
@@ -43,6 +65,11 @@ class CurriculumItem extends Component<CurriculumItemSignature> {
4365
}
4466
}
4567

68+
@action
69+
refresh() {
70+
this.args.onRefresh?.();
71+
}
72+
4673
goToCurriculum = () => {
4774
const curriculumId = this.args.curriculum.id;
4875
this.router.transitionTo('dashboard.curriculums.edit', curriculumId);
@@ -63,6 +90,7 @@ class CurriculumItem extends Component<CurriculumItemSignature> {
6390
<CurriculumMoreActions
6491
@curriculumId={{@curriculum.id}}
6592
@onRename={{this.focusTitle}}
93+
@onDelete={{this.refresh}}
6694
/>
6795
</div>
6896
</div>
@@ -73,10 +101,12 @@ class CurriculumItem extends Component<CurriculumItemSignature> {
73101
name="curriculum-title-{{@curriculum.id}}"
74102
class="font-medium pb-1 hover:text-blue-600 hover:underline hover:underline-offset-6 focus:outline-none transition-colors duration-200"
75103
value={{@curriculum.title}}
104+
{{on "keydown" this.renameOnEnter}}
105+
{{on "blur" this.renameOnBlur}}
76106
/>
77107
<span data-test-curriculum-last-modified class="text-sm text-gray-500">
78108
{{t "curriculums.view.lastModified"}}
79-
{{relativeTime @curriculum.lastModified}}
109+
{{relativeTime @curriculum.updatedAt}}
80110
</span>
81111
</div>
82112
</template>

@libs/users-front/src/components/curriculums/curriculum-list.gts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,59 @@ import CurriculumAdd from '#src/components/curriculums/curriculum-add.gts';
44
import { create, collection, clickable, text } from 'ember-cli-page-object';
55
import t from 'ember-intl/helpers/t';
66
import { CurriculumItemPageObject } from './curriculum-item.gts';
7+
import { service } from '@ember/service';
8+
import type CurriculumService from '#src/services/curriculum.ts';
9+
import type Owner from '@ember/owner';
10+
import { tracked } from '@glimmer/tracking';
11+
import type { Curriculum } from '#src/schemas/curriculums.ts';
712

813
interface CurriculumListSignature {
914
Element: HTMLDivElement;
10-
11-
Args: {
12-
curriculums?: Array<{
13-
id: number;
14-
title: string;
15-
lastModified: string;
16-
}>;
17-
};
15+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
16+
Args: {};
1817
}
1918

2019
class CurriculumList extends Component<CurriculumListSignature> {
21-
get curriculums() {
22-
const sortedCurriculums = [...(this.args.curriculums || [])].sort(
20+
@service declare curriculum: CurriculumService;
21+
@tracked curriculums: Curriculum[] = [];
22+
23+
constructor(owner: Owner, args: CurriculumListSignature['Args']) {
24+
super(owner, args);
25+
void this.loadCurriculums();
26+
}
27+
28+
async loadCurriculums() {
29+
this.curriculums = this.sortCurriculums(await this.curriculum.findAll());
30+
}
31+
32+
onRefresh = async () => {
33+
await this.loadCurriculums();
34+
};
35+
36+
sortCurriculums(curriculums: Curriculum[] = this.curriculums): Curriculum[] {
37+
const sortedCurriculums = [...(curriculums || [])].sort(
2338
(a, b) =>
24-
new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
39+
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
2540
);
2641
return sortedCurriculums;
2742
}
2843

44+
onAdd = async () => {
45+
await this.loadCurriculums();
46+
};
47+
2948
<template>
3049
<div data-test-curriculums-list class="flex flex-col gap-4">
3150
<h2 data-test-curriculums-title class="text-xl font-bold">
3251
{{t "curriculums.view.curriculumsVitae"}}
3352
</h2>
3453
<div class="flex flex-row flex-wrap">
35-
<CurriculumAdd />
54+
<CurriculumAdd @onAdd={{this.onAdd}} />
3655
{{#each this.curriculums as |curriculum|}}
37-
<CurriculumItem @curriculum={{curriculum}} />
56+
<CurriculumItem
57+
@curriculum={{curriculum}}
58+
@onRefresh={{this.onRefresh}}
59+
/>
3860
{{/each}}
3961
</div>
4062
</div>

@libs/users-front/src/components/curriculums/curriculum-more-actions.gts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ import DuplicateIcon from '#src/assets/icons/duplicate.gts';
1313
import DownloadIcon from '#src/assets/icons/download.gts';
1414
import DeleteIcon from '#src/assets/icons/delete.gts';
1515
import { clickable, create, isVisible } from 'ember-cli-page-object';
16+
import type CurriculumService from '#src/services/curriculum.ts';
1617

1718
interface CurriculumMoreActionsSignature {
1819
Args: {
19-
curriculumId: number;
20+
curriculumId: string | null;
2021
onRename: () => void;
22+
onDelete: () => void;
2123
};
2224
}
2325

2426
class CurriculumMoreActions extends Component<CurriculumMoreActionsSignature> {
2527
@service declare router: RouterService;
28+
@service declare curriculum: CurriculumService;
2629

2730
@tracked isOpen = false;
2831

@@ -31,6 +34,16 @@ class CurriculumMoreActions extends Component<CurriculumMoreActionsSignature> {
3134
this.isOpen = !this.isOpen;
3235
}
3336

37+
@action
38+
async delete(event: MouseEvent) {
39+
event.stopPropagation();
40+
this.isOpen = false;
41+
42+
if (!this.args.curriculumId) return;
43+
await this.curriculum.delete(this.args.curriculumId);
44+
this.args.onDelete?.();
45+
}
46+
3447
@action
3548
rename(event: MouseEvent) {
3649
event.stopPropagation();
@@ -105,6 +118,7 @@ class CurriculumMoreActions extends Component<CurriculumMoreActionsSignature> {
105118

106119
<button
107120
data-test-curriculum-more-action-delete-button
121+
{{on "click" this.delete}}
108122
type="button"
109123
class="w-full text-left px-4 py-2 flex flex-row items-center cursor-pointer hover:bg-gray-100"
110124
>

0 commit comments

Comments
 (0)