@@ -66,8 +71,8 @@
diff --git a/src/components/tables/TableHeadingSortableColumn.vue b/src/components/tables/TableHeadingSortableColumn.vue
new file mode 100644
index 0000000..e63dfe2
--- /dev/null
+++ b/src/components/tables/TableHeadingSortableColumn.vue
@@ -0,0 +1,70 @@
+
+
+
+ |
+
+ |
+
+
+
diff --git a/src/db/drizzle/0000_open_the_twelve.sql b/src/db/drizzle/0000_unusual_karen_page.sql
similarity index 92%
rename from src/db/drizzle/0000_open_the_twelve.sql
rename to src/db/drizzle/0000_unusual_karen_page.sql
index 37a027b..6f5591f 100644
--- a/src/db/drizzle/0000_open_the_twelve.sql
+++ b/src/db/drizzle/0000_unusual_karen_page.sql
@@ -1,7 +1,7 @@
CREATE TABLE `diary_entries` (
`art_work_id` integer NOT NULL,
- `date_created` text(10) NOT NULL,
- `date_modified` text(10) NOT NULL,
+ `date_created` integer NOT NULL,
+ `date_modified` integer NOT NULL,
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`state_id` integer NOT NULL,
FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action,
@@ -24,13 +24,16 @@ CREATE TABLE `diary_entries_states` (
CREATE UNIQUE INDEX `diary_entries_states_state_unique` ON `diary_entries_states` (`state`);--> statement-breakpoint
CREATE TABLE `viewings` (
`art_work_id` integer NOT NULL,
- `date` text(10) NOT NULL,
+ `date` integer NOT NULL,
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
CREATE TABLE `art_works` (
`cover_path` text,
+ `date_created` integer NOT NULL,
+ `date_metadata_updated` integer NOT NULL,
+ `date_updated` integer NOT NULL,
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`medium_type_id` integer NOT NULL,
`name` text NOT NULL,
diff --git a/src/db/drizzle/meta/0000_snapshot.json b/src/db/drizzle/meta/0000_snapshot.json
index b0681c4..0923fb6 100644
--- a/src/db/drizzle/meta/0000_snapshot.json
+++ b/src/db/drizzle/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "da9e1cf6-aba7-4b5a-a839-3b0fe3dce876",
+ "id": "8b217318-7662-4f81-bc55-92d5c6ddf2b2",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"diary_entries": {
@@ -16,14 +16,14 @@
},
"date_created": {
"name": "date_created",
- "type": "text(10)",
+ "type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"date_modified": {
"name": "date_modified",
- "type": "text(10)",
+ "type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@@ -178,7 +178,7 @@
},
"date": {
"name": "date",
- "type": "text(10)",
+ "type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@@ -221,6 +221,27 @@
"notNull": false,
"autoincrement": false
},
+ "date_created": {
+ "name": "date_created",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "date_metadata_updated": {
+ "name": "date_metadata_updated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "date_updated": {
+ "name": "date_updated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
"id": {
"name": "id",
"type": "integer",
diff --git a/src/db/drizzle/meta/_journal.json b/src/db/drizzle/meta/_journal.json
index fde85a2..de36f89 100644
--- a/src/db/drizzle/meta/_journal.json
+++ b/src/db/drizzle/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1740666437095,
- "tag": "0000_open_the_twelve",
+ "when": 1740814587298,
+ "tag": "0000_unusual_karen_page",
"breakpoints": true
}
]
diff --git a/src/db/schemas/entries.ts b/src/db/schemas/entries.ts
index 2ffa35f..3ae3683 100644
--- a/src/db/schemas/entries.ts
+++ b/src/db/schemas/entries.ts
@@ -12,8 +12,8 @@ import { ArtWorks, Genres } from "./works";
export const DiaryEntries = table("diary_entries", {
artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id).notNull(),
- dateCreated: t.text("date_created", { length: 10 }).notNull(),
- dateModified: t.text("date_modified", { length: 10 }).notNull(),
+ dateCreated: t.integer("date_created", { mode: "timestamp" }).notNull(),
+ dateModified: t.integer("date_modified", { mode: "timestamp" }).notNull(),
id: t.integer("id").primaryKey({ autoIncrement: true }),
stateId: t.integer("state_id").references((): AnySQLiteColumn => DiaryEntriesStates.id).notNull(),
});
@@ -31,7 +31,7 @@ export const DiaryEntriesStates = table("diary_entries_states", {
export const Viewings = table("viewings", {
artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id).notNull(),
- date: t.text("date", { length: 10 }).notNull(),
+ date: t.integer("date", { mode: "timestamp" }).notNull(),
id: t.integer("id").primaryKey({ autoIncrement: true }),
});
@@ -39,8 +39,8 @@ export const Viewings = table("viewings", {
export const DiaryEntrySchema = Schema.Struct({
artWorkId: Schema.NonNegativeInt,
- dateCreated: Schema.String,
- dateModified: Schema.String,
+ dateCreated: Schema.Number,
+ dateModified: Schema.Number,
id: Schema.NonNegativeInt,
stateId: Schema.NonNegativeInt,
});
@@ -61,7 +61,7 @@ export type DiaryEntryState = Schema.Schema.Type;
export const ViewingSchema = Schema.Struct({
artWorkId: Schema.NonNegativeInt,
- date: Schema.String,
+ date: Schema.Number,
id: Schema.NonNegativeInt,
});
export type Viewing = Schema.Schema.Type;
diff --git a/src/db/schemas/works.ts b/src/db/schemas/works.ts
index ea04fac..f08dcd5 100644
--- a/src/db/schemas/works.ts
+++ b/src/db/schemas/works.ts
@@ -17,6 +17,9 @@ export const MediaTypes = table("media_types", {
export const ArtWorks = table("art_works", {
coverPath: t.text("cover_path").unique(),
+ dateCreated: t.integer("date_created", { mode: "timestamp" }).notNull(),
+ dateMetadataUpdated: t.integer("date_metadata_updated", { mode: "timestamp" }).notNull(),
+ dateUpdated: t.integer("date_updated", { mode: "timestamp" }).notNull(),
id: t.integer("id").primaryKey({ autoIncrement: true }),
mediumTypeId: t.integer("medium_type_id").references((): AnySQLiteColumn => MediaTypes.id).notNull(),
name: t.text("name").notNull(),
@@ -45,6 +48,12 @@ export type MediaType = Schema.Schema.Type;
export const ArtWorkSchema = Schema.Struct({
/** Le chemin de l'image de la pochette de l'oeuvre d'art. */
coverPath: Schema.Union(Schema.String, Schema.Null),
+ /** La date de création de l'entrée. */
+ dateCreated: Schema.Number,
+ /** La date de dernière mise à jour des métadonnées de l'entrée depuis l'API TMDB. */
+ dateMetadataUpdated: Schema.Number,
+ /** La date de dernière mise à jour de l'entrée. */
+ dateUpdated: Schema.Number,
/** L'ID numérique de l'ouvre d'art. */
id: Schema.NonNegativeInt,
/** L'ID numérique du type de l'oeuvre d'art. */
diff --git a/src/libs/apis/tmdb/orders.ts b/src/libs/apis/tmdb/orders.ts
index a31139e..3cba5b4 100644
--- a/src/libs/apis/tmdb/orders.ts
+++ b/src/libs/apis/tmdb/orders.ts
@@ -1,22 +1,63 @@
import type { MergedTmdbLocalData } from "@/libs/search/schemas";
+import type { AriaSortValues } from "@/libs/search/types";
+import type { Values } from "@/libs/utils/types";
-import { Order } from "effect";
+import { ARIA_SORT_VALUES } from "@/libs/search/constants";
+import { Match, Order, pipe } from "effect";
-import type { TmdbMovieSearchResponseResult } from "./schemas";
+export const TMDB_SORT_VALUES = {
+ ORIGINAL: "original",
+ POPULARITY: "popularity",
+ RELEASE_DATE: "release_date",
+ TITLE: "title",
+} as const;
-export const byTitle = Order.mapInput(
- Order.string,
- (tmdbEntry: TmdbMovieSearchResponseResult) => tmdbEntry.original_title,
+export type TmdbSortValues = Values;
+
+export interface TmdbSortData {
+ sortOrder: AriaSortValues;
+ sortValue: TmdbSortValues;
+}
+
+export const toggleSortOrder = (order: AriaSortValues): AriaSortValues =>
+ Match.value(order).pipe(
+ Match.when(ARIA_SORT_VALUES.ASCENDING, () => ARIA_SORT_VALUES.DESCENDING),
+ Match.when(ARIA_SORT_VALUES.DESCENDING, () => ARIA_SORT_VALUES.ASCENDING),
+ Match.when(ARIA_SORT_VALUES.NONE, () => ARIA_SORT_VALUES.ASCENDING),
+ Match.exhaustive,
+ );
+
+export const getTmdbSortFunction = (sortData: TmdbSortData) =>
+ pipe(
+ // Récupère la fonction de tri correspondant à la propriété demandé.
+ Match.value(sortData.sortValue).pipe(
+ Match.when(TMDB_SORT_VALUES.ORIGINAL, () => byOriginalIndexAscending),
+ Match.when(TMDB_SORT_VALUES.POPULARITY, () => byPopularityAscending),
+ Match.when(TMDB_SORT_VALUES.RELEASE_DATE, () => byReleaseDateAscending),
+ Match.when(TMDB_SORT_VALUES.TITLE, () => byTitleAscending),
+ Match.orElse(() => byTitleAscending),
+ ),
+ // Applique le bon sens (ascendant/descendant).
+ (sortFunction: Order.Order<[number, MergedTmdbLocalData]>) =>
+ Match.value(sortData.sortOrder).pipe(
+ Match.when(ARIA_SORT_VALUES.DESCENDING, () => Order.reverse(sortFunction)),
+ Match.orElse(() => sortFunction),
+ ),
+ );
+
+export const byOriginalIndexAscending = Order.mapInput(
+ Order.number,
+ (data: [number, MergedTmdbLocalData]) => data[1].original_result_index,
);
-
-export const byReleaseDate = Order.mapInput(
- Order.string,
- (tmdbEntry: TmdbMovieSearchResponseResult) => tmdbEntry.release_date,
+export const byPopularityAscending = Order.mapInput(
+ Order.number,
+ (data: [number, MergedTmdbLocalData]) => data[1].popularity,
);
-
-// Tuples
-
-export const tupleByTitle = Order.mapInput(
+export const byReleaseDateAscending = Order.mapInput(
+ Order.string,
+ (data: [number, MergedTmdbLocalData]) => data[1].release_date,
+);
+export const byTitleAscending = Order.mapInput(
Order.string,
(data: [number, MergedTmdbLocalData]) => data[1].title,
);
diff --git a/src/libs/search/constants.ts b/src/libs/search/constants.ts
new file mode 100644
index 0000000..5c7dfbd
--- /dev/null
+++ b/src/libs/search/constants.ts
@@ -0,0 +1,5 @@
+export const ARIA_SORT_VALUES = {
+ ASCENDING: "ascending",
+ DESCENDING: "descending",
+ NONE: "none",
+} as const;
diff --git a/src/libs/search/schemas.ts b/src/libs/search/schemas.ts
index 19f6d7e..aaef875 100644
--- a/src/libs/search/schemas.ts
+++ b/src/libs/search/schemas.ts
@@ -11,12 +11,13 @@ export class MergedTmdbLocalData extends Schema.Class("Merg
artWorkCoverPath: Schema.Union(Schema.String, Schema.Null),
artWorkId: Schema.NonNegativeInt.pipe(Schema.optional),
artWorkMediumTypeId: Schema.NonNegativeInt.pipe(Schema.optional),
- entryDateCreated: Schema.String.pipe(Schema.optional),
- entryDateModified: Schema.String.pipe(Schema.optional),
+ entryDateCreated: Schema.Date.pipe(Schema.optional),
+ entryDateModified: Schema.Date.pipe(Schema.optional),
entryId: Schema.NonNegativeInt.pipe(Schema.optional),
entryStateId: Schema.NonNegativeInt.pipe(Schema.optional),
genre_ids: Schema.Array(Schema.NonNegativeInt),
original_language: Schema.String,
+ original_result_index: Schema.Int,
original_title: Schema.String,
overview: Schema.String,
popularity: Schema.Number,
diff --git a/src/libs/search/search.ts b/src/libs/search/search.ts
index 888fc3f..3921785 100644
--- a/src/libs/search/search.ts
+++ b/src/libs/search/search.ts
@@ -1,7 +1,13 @@
import type { NonEmptyArray } from "effect/Array";
+import type { Router } from "vue-router";
+import { PrettyLogger } from "@/services/logger";
import { UrlParams } from "@effect/platform";
-import { Effect, pipe } from "effect";
+import { Effect, Match, pipe } from "effect";
+
+import type { AriaSortValues } from "./types";
+
+import { ARIA_SORT_VALUES } from "./constants";
/**
* Transforme les valeurs d'un `FormData` en `Record` trié.
@@ -9,7 +15,7 @@ import { Effect, pipe } from "effect";
* @param formData Les valeurs d'un formulaire.
* @returns Un `Effect` des valeurs.
*/
-const formDataToRecord = (formData: FormData): Effect.Effect | string>> =>
+export const formDataToRecord = (formData: FormData): Effect.Effect | string>> =>
pipe(
Effect.succeed(Array.from(formData.entries())),
// @ts-expect-error -- Impossible de typer les valeurs de FormData comme string.
@@ -23,4 +29,30 @@ const formDataToRecord = (formData: FormData): Effect.Effect UrlParams.toRecord(urlParams)),
);
-export default { formDataToRecord };
+export const updateSortOrder = (sortOrder: AriaSortValues) =>
+ Match.value(sortOrder).pipe(
+ Match.when(ARIA_SORT_VALUES.NONE, () => ARIA_SORT_VALUES.ASCENDING),
+ Match.when(ARIA_SORT_VALUES.ASCENDING, () => ARIA_SORT_VALUES.DESCENDING),
+ Match.when(ARIA_SORT_VALUES.DESCENDING, () => ARIA_SORT_VALUES.ASCENDING),
+ Match.exhaustive,
+ );
+
+export const updateUrlQueryFromFormData =
+ (router: Router, form: HTMLFormElement | null) => async (event?: Event): Promise => {
+ event?.preventDefault();
+
+ await pipe(
+ // Garantis que l'Élément soit bien présent.
+ Effect.fromNullable(form),
+ Effect.andThen((form: HTMLFormElement) => new FormData(form)),
+ Effect.andThen((searchFormData: FormData) => formDataToRecord(searchFormData)),
+ // Met à jour les paramètres de l'URL.
+ Effect.tap((routeQueryParams: Record | string>) =>
+ router.push({ force: true, query: routeQueryParams })
+ ),
+ Effect.tapError(Effect.logError),
+ Effect.ignore,
+ Effect.provide(PrettyLogger),
+ Effect.runPromise,
+ );
+ };
diff --git a/src/libs/search/types.d.ts b/src/libs/search/types.d.ts
deleted file mode 100644
index 579e427..0000000
--- a/src/libs/search/types.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type { ArtWork, DiaryEntry } from "@/db/schemas";
-
-import type { TmdbMovieSearchResponseResult } from "../apis/tmdb/schemas";
-
-/** Page de réponse de l'API TMDB avec les données locales correspondantes. */
-export interface TmdbDataWithLocalData {
- artWork?: ArtWork;
- entry?: DiaryEntry;
- tmdbData: TmdbMovieSearchResponseResult;
-}
diff --git a/src/libs/search/types.ts b/src/libs/search/types.ts
new file mode 100644
index 0000000..180fe5c
--- /dev/null
+++ b/src/libs/search/types.ts
@@ -0,0 +1,4 @@
+import type { Values } from "../utils/types";
+import type { ARIA_SORT_VALUES } from "./constants";
+
+export type AriaSortValues = Values;
diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue
index 426d26e..4d6ac39 100644
--- a/src/pages/HomePage.vue
+++ b/src/pages/HomePage.vue
@@ -1,9 +1,10 @@
@@ -251,9 +250,11 @@
Récupération des résultats
{{ message }}
-
+
+
+