@umituz/react-native-ai-generation-content 1.17.146 → 1.17.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +3 -3
  3. package/src/domains/creations/index.ts +1 -1
  4. package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +6 -6
  5. package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +5 -5
  6. package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +5 -5
  7. package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +1 -1
  8. package/src/domains/creations/presentation/components/GalleryHeader.tsx +1 -1
  9. package/src/domains/creations/presentation/hooks/advancedFilter.types.ts +38 -0
  10. package/src/domains/creations/presentation/hooks/filterHelpers.ts +145 -0
  11. package/src/domains/creations/presentation/hooks/index.ts +5 -0
  12. package/src/domains/creations/presentation/hooks/useAdvancedFilter.ts +34 -184
  13. package/src/domains/flashcard-generation/parsers/flashcard-response.parser.ts +1 -1
  14. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +10 -10
  15. package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +8 -8
  16. package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +5 -5
  17. package/src/features/meme-generator/infrastructure/services/MemeGenerationService.ts +2 -2
  18. package/src/features/script-generator/presentation/hooks/useScriptGenerator.ts +1 -1
  19. package/src/features/text-to-image/infrastructure/services/text-to-image-executor.ts +2 -2
  20. package/src/features/text-to-image/presentation/hooks/useGeneration.ts +12 -12
  21. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +9 -9
  22. package/src/features/text-to-video/presentation/hooks/textToVideoExecution.ts +134 -0
  23. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +24 -118
  24. package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +1 -1
  25. package/src/features/text-to-voice/infrastructure/services/text-to-voice-executor.ts +2 -2
  26. package/src/index.ts +1 -1
  27. package/src/infrastructure/config/app-services.config.ts +2 -2
  28. package/src/infrastructure/orchestration/GenerationOrchestrator.ts +23 -114
  29. package/src/infrastructure/orchestration/index.ts +3 -1
  30. package/src/infrastructure/orchestration/orchestrator.errors.ts +29 -0
  31. package/src/infrastructure/orchestration/orchestrator.types.ts +48 -0
  32. package/src/infrastructure/services/generation-orchestrator.service.ts +5 -5
  33. package/src/infrastructure/services/image-feature-executor.service.ts +2 -2
  34. package/src/infrastructure/services/job-poller.service.ts +1 -1
  35. package/src/infrastructure/services/job-poller.ts +3 -3
  36. package/src/infrastructure/services/provider-registry.service.ts +6 -6
  37. package/src/infrastructure/services/provider-validator.ts +4 -4
  38. package/src/infrastructure/services/video-feature-executor.service.ts +2 -2
  39. package/src/infrastructure/utils/error-classifier.util.ts +2 -2
  40. package/src/infrastructure/utils/feature-utils.ts +4 -4
  41. package/src/infrastructure/utils/result-validator.util.ts +1 -1
  42. package/src/infrastructure/utils/video-helpers.ts +3 -3
  43. package/src/presentation/components/AIGenerationForm.tsx +2 -2
  44. package/src/presentation/components/buttons/GenerateButton.tsx +3 -3
  45. package/src/presentation/hooks/useGenerationCallbacksBuilder.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.146",
3
+ "version": "1.17.147",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -44,7 +44,7 @@ class TextModerator extends BaseModerator {
44
44
 
45
45
  moderate(content: string): ModerationResult {
46
46
  if (typeof __DEV__ !== "undefined" && __DEV__) {
47
- // eslint-disable-next-line no-console
47
+
48
48
  console.log("[TextModerator] moderate() called", {
49
49
  contentLength: content?.length ?? 0,
50
50
  });
@@ -53,7 +53,7 @@ class TextModerator extends BaseModerator {
53
53
  const validationError = this.validate(content);
54
54
  if (validationError) {
55
55
  if (typeof __DEV__ !== "undefined" && __DEV__) {
56
- // eslint-disable-next-line no-console
56
+
57
57
  console.log("[TextModerator] validation failed", {
58
58
  ruleId: validationError.ruleId,
59
59
  violationType: validationError.violationType,
@@ -65,7 +65,7 @@ class TextModerator extends BaseModerator {
65
65
  const violations = this.evaluateRules(content);
66
66
 
67
67
  if (typeof __DEV__ !== "undefined" && __DEV__) {
68
- // eslint-disable-next-line no-console
68
+
69
69
  console.log("[TextModerator] moderate() completed", {
70
70
  isAllowed: violations.length === 0,
71
71
  violationsCount: violations.length,
@@ -3,7 +3,7 @@
3
3
  * AI-generated creations gallery with filtering, sharing, and management
4
4
  */
5
5
 
6
- // eslint-disable-next-line no-console
6
+
7
7
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] creations/index.ts - Module loading");
8
8
 
9
9
  // =============================================================================
@@ -17,7 +17,7 @@ export class CreationsFetcher {
17
17
 
18
18
  async getAll(userId: string): Promise<Creation[]> {
19
19
  if (__DEV__) {
20
- // eslint-disable-next-line no-console
20
+
21
21
  console.log("[CreationsRepository] getAll()", { userId });
22
22
  }
23
23
 
@@ -29,7 +29,7 @@ export class CreationsFetcher {
29
29
  const snapshot = await getDocs(q);
30
30
 
31
31
  if (__DEV__) {
32
- // eslint-disable-next-line no-console
32
+
33
33
  console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
34
34
  }
35
35
 
@@ -39,7 +39,7 @@ export class CreationsFetcher {
39
39
  });
40
40
  } catch (error) {
41
41
  if (__DEV__) {
42
- // eslint-disable-next-line no-console
42
+
43
43
  console.error("[CreationsRepository] getAll() ERROR", error);
44
44
  }
45
45
  return [];
@@ -48,7 +48,7 @@ export class CreationsFetcher {
48
48
 
49
49
  async getById(userId: string, id: string): Promise<Creation | null> {
50
50
  if (__DEV__) {
51
- // eslint-disable-next-line no-console
51
+
52
52
  console.log("[CreationsRepository] getById()", { userId, id });
53
53
  }
54
54
 
@@ -60,7 +60,7 @@ export class CreationsFetcher {
60
60
 
61
61
  if (!docSnap.exists()) {
62
62
  if (__DEV__) {
63
- // eslint-disable-next-line no-console
63
+
64
64
  console.log("[CreationsRepository] Document not found");
65
65
  }
66
66
  return null;
@@ -70,7 +70,7 @@ export class CreationsFetcher {
70
70
  return this.documentMapper(docSnap.id, data);
71
71
  } catch (error) {
72
72
  if (__DEV__) {
73
- // eslint-disable-next-line no-console
73
+
74
74
  console.error("[CreationsRepository] getById() ERROR", error);
75
75
  }
76
76
  return null;
@@ -1,4 +1,4 @@
1
- // eslint-disable-next-line no-console
1
+
2
2
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository.ts - Module loading");
3
3
 
4
4
  import { BaseRepository, FirestorePathResolver } from "@umituz/react-native-firebase";
@@ -39,22 +39,22 @@ export class CreationsRepository
39
39
  collectionName: string,
40
40
  options?: RepositoryOptions,
41
41
  ) {
42
- // eslint-disable-next-line no-console
42
+
43
43
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Constructor start");
44
44
  super();
45
45
 
46
46
  const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
47
47
 
48
- // eslint-disable-next-line no-console
48
+
49
49
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Getting db");
50
50
  const db = this.getDb();
51
- // eslint-disable-next-line no-console
51
+
52
52
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - db:", db ? "available" : "null");
53
53
 
54
54
  this.pathResolver = new FirestorePathResolver(collectionName, db);
55
55
  this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
56
56
  this.writer = new CreationsWriter(this.pathResolver);
57
- // eslint-disable-next-line no-console
57
+
58
58
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] CreationsRepository - Constructor end");
59
59
  }
60
60
 
@@ -13,7 +13,7 @@ export class CreationsWriter {
13
13
 
14
14
  async create(userId: string, creation: Creation): Promise<void> {
15
15
  if (typeof __DEV__ !== "undefined" && __DEV__) {
16
- // eslint-disable-next-line no-console
16
+
17
17
  console.log("[CreationsWriter] create() start", { userId, creationId: creation.id });
18
18
  }
19
19
 
@@ -35,12 +35,12 @@ export class CreationsWriter {
35
35
  try {
36
36
  await setDoc(docRef, data);
37
37
  if (typeof __DEV__ !== "undefined" && __DEV__) {
38
- // eslint-disable-next-line no-console
38
+
39
39
  console.log("[CreationsWriter] create() success", { creationId: creation.id });
40
40
  }
41
41
  } catch (error) {
42
42
  if (typeof __DEV__ !== "undefined" && __DEV__) {
43
- // eslint-disable-next-line no-console
43
+
44
44
  console.error("[CreationsWriter] create() error", error);
45
45
  }
46
46
  throw error;
@@ -53,7 +53,7 @@ export class CreationsWriter {
53
53
  updates: Partial<Creation>,
54
54
  ): Promise<boolean> {
55
55
  if (__DEV__) {
56
- // eslint-disable-next-line no-console
56
+
57
57
  console.log("[CreationsRepository] update()", { userId, id, updates });
58
58
  }
59
59
 
@@ -89,7 +89,7 @@ export class CreationsWriter {
89
89
  return true;
90
90
  } catch (error) {
91
91
  if (__DEV__) {
92
- // eslint-disable-next-line no-console
92
+
93
93
  console.error("[CreationsRepository] update() ERROR", error);
94
94
  }
95
95
  return false;
@@ -36,7 +36,7 @@ export function CreationsHomeCard({
36
36
 
37
37
 
38
38
  if (__DEV__) {
39
- // eslint-disable-next-line no-console
39
+
40
40
  console.log("[CreationsHomeCard] Render:", {
41
41
  isLoading,
42
42
  count: creations?.length ?? 0,
@@ -47,7 +47,7 @@ export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
47
47
  key={btn.id}
48
48
  onPress={() => {
49
49
  if (__DEV__) {
50
- // eslint-disable-next-line no-console
50
+
51
51
  console.log(`[GalleryHeader] ${btn.id} filter pressed`);
52
52
  }
53
53
  btn.onPress();
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Advanced Filter Types
3
+ * Type definitions for advanced filtering hook
4
+ */
5
+
6
+ import type { CreationFilter, CreationStats, FilterOption } from "../../domain/types";
7
+ import type { CreationCategory, CreationStatus, CreationTypeId } from "../../domain/types";
8
+
9
+ export interface FilterableCreation {
10
+ id: string;
11
+ type?: string;
12
+ status?: string;
13
+ prompt?: string;
14
+ createdAt?: Date | number;
15
+ updatedAt?: Date | number;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ export interface UseAdvancedFilterProps<T extends FilterableCreation> {
20
+ creations: T[] | undefined;
21
+ initialFilter?: Partial<CreationFilter>;
22
+ }
23
+
24
+ export interface UseAdvancedFilterReturn<T extends FilterableCreation> {
25
+ filtered: T[];
26
+ filter: CreationFilter;
27
+ stats: CreationStats;
28
+ activeMediaFilter: string;
29
+ activeStatusFilter: string;
30
+ hasActiveFilters: boolean;
31
+ mediaFilterOptions: FilterOption[];
32
+ statusFilterOptions: FilterOption[];
33
+ setMediaFilter: (filter: CreationCategory | CreationTypeId) => void;
34
+ setStatusFilter: (status: CreationStatus | "all") => void;
35
+ setSearchQuery: (query: string) => void;
36
+ updateFilter: (update: Partial<CreationFilter>) => void;
37
+ resetFilters: () => void;
38
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Filter Helpers
3
+ * Utility functions for filtering and sorting creations
4
+ */
5
+
6
+ import type { CreationFilter } from "../../domain/types";
7
+ import { isTypeInCategory } from "../../domain/types";
8
+ import type { CreationCategory, CreationTypeId } from "../../domain/types";
9
+ import type { FilterableCreation } from "./advancedFilter.types";
10
+
11
+ export function filterByType<T extends FilterableCreation>(
12
+ creations: T[],
13
+ filterType: string | undefined,
14
+ ): T[] {
15
+ if (!filterType || filterType === "all") return creations;
16
+
17
+ if (["image", "video", "voice"].includes(filterType)) {
18
+ const category = filterType as CreationCategory;
19
+ return creations.filter(
20
+ (c) => c.type && isTypeInCategory(c.type as CreationTypeId, category),
21
+ );
22
+ }
23
+
24
+ return creations.filter((c) => c.type === filterType);
25
+ }
26
+
27
+ export function filterByStatus<T extends FilterableCreation>(
28
+ creations: T[],
29
+ status: string | undefined,
30
+ ): T[] {
31
+ if (!status || status === "all") return creations;
32
+ return creations.filter((c) => c.status === status);
33
+ }
34
+
35
+ export function filterBySearch<T extends FilterableCreation>(
36
+ creations: T[],
37
+ searchQuery: string | undefined,
38
+ ): T[] {
39
+ if (!searchQuery?.trim()) return creations;
40
+
41
+ const query = searchQuery.toLowerCase().trim();
42
+ return creations.filter((c) => {
43
+ const prompt = c.prompt?.toLowerCase() || "";
44
+ const type = c.type?.toLowerCase() || "";
45
+ return prompt.includes(query) || type.includes(query);
46
+ });
47
+ }
48
+
49
+ export function filterByDateRange<T extends FilterableCreation>(
50
+ creations: T[],
51
+ startDate: number | undefined,
52
+ endDate: number | undefined,
53
+ ): T[] {
54
+ let result = creations;
55
+
56
+ if (startDate) {
57
+ result = result.filter((c) => {
58
+ const createdAt =
59
+ c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
60
+ return createdAt >= startDate;
61
+ });
62
+ }
63
+
64
+ if (endDate) {
65
+ result = result.filter((c) => {
66
+ const createdAt =
67
+ c.createdAt instanceof Date ? c.createdAt.getTime() : c.createdAt || 0;
68
+ return createdAt <= endDate;
69
+ });
70
+ }
71
+
72
+ return result;
73
+ }
74
+
75
+ export function sortCreations<T extends FilterableCreation>(
76
+ creations: T[],
77
+ sortField: CreationFilter["sortField"],
78
+ sortOrder: CreationFilter["sortOrder"],
79
+ ): T[] {
80
+ if (!sortField) return creations;
81
+
82
+ return [...creations].sort((a, b) => {
83
+ let aVal: string | number | Date | undefined;
84
+ let bVal: string | number | Date | undefined;
85
+
86
+ switch (sortField) {
87
+ case "createdAt":
88
+ aVal = a.createdAt;
89
+ bVal = b.createdAt;
90
+ break;
91
+ case "updatedAt":
92
+ aVal = a.updatedAt;
93
+ bVal = b.updatedAt;
94
+ break;
95
+ case "type":
96
+ aVal = a.type;
97
+ bVal = b.type;
98
+ break;
99
+ case "status":
100
+ aVal = a.status;
101
+ bVal = b.status;
102
+ break;
103
+ default:
104
+ return 0;
105
+ }
106
+
107
+ if (aVal instanceof Date) aVal = aVal.getTime();
108
+ if (bVal instanceof Date) bVal = bVal.getTime();
109
+
110
+ if (aVal === undefined && bVal === undefined) return 0;
111
+ if (aVal === undefined) return 1;
112
+ if (bVal === undefined) return -1;
113
+
114
+ if (typeof aVal === "string" && typeof bVal === "string") {
115
+ return sortOrder === "desc"
116
+ ? bVal.localeCompare(aVal)
117
+ : aVal.localeCompare(bVal);
118
+ }
119
+
120
+ if (typeof aVal === "number" && typeof bVal === "number") {
121
+ return sortOrder === "desc" ? bVal - aVal : aVal - bVal;
122
+ }
123
+
124
+ return 0;
125
+ });
126
+ }
127
+
128
+ export function applyAllFilters<T extends FilterableCreation>(
129
+ creations: T[],
130
+ filter: CreationFilter,
131
+ ): T[] {
132
+ let result = [...creations];
133
+
134
+ result = filterByType(result, filter.type);
135
+ result = filterByStatus(result, filter.status);
136
+ result = filterBySearch(result, filter.searchQuery);
137
+ result = filterByDateRange(result, filter.startDate, filter.endDate);
138
+ result = sortCreations(result, filter.sortField, filter.sortOrder);
139
+
140
+ if (filter.limit && filter.limit > 0) {
141
+ result = result.slice(0, filter.limit);
142
+ }
143
+
144
+ return result;
145
+ }
@@ -6,6 +6,11 @@ export { useCreations } from "./useCreations";
6
6
  export { useDeleteCreation } from "./useDeleteCreation";
7
7
  export { useCreationsFilter } from "./useCreationsFilter";
8
8
  export { useAdvancedFilter } from "./useAdvancedFilter";
9
+ export type {
10
+ FilterableCreation,
11
+ UseAdvancedFilterProps,
12
+ UseAdvancedFilterReturn,
13
+ } from "./advancedFilter.types";
9
14
  export { useFilter, useStatusFilter, useMediaFilter } from "./useFilter";
10
15
  export type { UseFilterProps, UseFilterReturn } from "./useFilter";
11
16
  export { useGalleryFilters } from "./useGalleryFilters";
@@ -4,63 +4,29 @@
4
4
  */
5
5
 
6
6
  import { useState, useMemo, useCallback } from "react";
7
- import type { CreationFilter, CreationStats, FilterOption } from "../../domain/types";
7
+ import type { CreationFilter, FilterOption } from "../../domain/types";
8
8
  import {
9
9
  DEFAULT_CREATION_FILTER,
10
10
  MEDIA_FILTER_OPTIONS,
11
11
  STATUS_FILTER_OPTIONS,
12
12
  calculateCreationStats,
13
- isTypeInCategory,
14
13
  } from "../../domain/types";
15
14
  import type { CreationCategory, CreationStatus, CreationTypeId } from "../../domain/types";
16
-
17
- interface Creation {
18
- id: string;
19
- type?: string;
20
- status?: string;
21
- prompt?: string;
22
- createdAt?: Date | number;
23
- updatedAt?: Date | number;
24
- metadata?: Record<string, unknown>;
25
- }
26
-
27
- interface UseAdvancedFilterProps<T extends Creation> {
28
- creations: T[] | undefined;
29
- initialFilter?: Partial<CreationFilter>;
30
- }
31
-
32
- interface UseAdvancedFilterReturn<T extends Creation> {
33
- // Filtered results
34
- filtered: T[];
35
-
36
- // Current filter state
37
- filter: CreationFilter;
38
-
39
- // Stats
40
- stats: CreationStats;
41
-
42
- // Filter state
43
- activeMediaFilter: string;
44
- activeStatusFilter: string;
45
- hasActiveFilters: boolean;
46
-
47
- // Filter options with counts
48
- mediaFilterOptions: FilterOption[];
49
- statusFilterOptions: FilterOption[];
50
-
51
- // Actions
52
- setMediaFilter: (filter: CreationCategory | CreationTypeId) => void;
53
- setStatusFilter: (status: CreationStatus | "all") => void;
54
- setSearchQuery: (query: string) => void;
55
- updateFilter: (update: Partial<CreationFilter>) => void;
56
- resetFilters: () => void;
57
- }
58
-
59
- /**
60
- * Advanced filtering hook for creations
61
- * Supports category, status, search, and sorting
62
- */
63
- export function useAdvancedFilter<T extends Creation>({
15
+ import type {
16
+ FilterableCreation,
17
+ UseAdvancedFilterProps,
18
+ UseAdvancedFilterReturn,
19
+ } from "./advancedFilter.types";
20
+ import { applyAllFilters } from "./filterHelpers";
21
+
22
+ const EMPTY_STATS = {
23
+ total: 0,
24
+ byCategory: { all: 0, image: 0, video: 0, voice: 0 },
25
+ byStatus: { pending: 0, queued: 0, processing: 0, completed: 0, failed: 0 },
26
+ byType: {},
27
+ };
28
+
29
+ export function useAdvancedFilter<T extends FilterableCreation>({
64
30
  creations,
65
31
  initialFilter,
66
32
  }: UseAdvancedFilterProps<T>): UseAdvancedFilterReturn<T> {
@@ -69,158 +35,42 @@ export function useAdvancedFilter<T extends Creation>({
69
35
  ...initialFilter,
70
36
  });
71
37
 
72
- // Calculate stats from all creations
73
38
  const stats = useMemo(() => {
74
- if (!creations) {
75
- return {
76
- total: 0,
77
- byCategory: { all: 0, image: 0, video: 0, voice: 0 },
78
- byStatus: { pending: 0, queued: 0, processing: 0, completed: 0, failed: 0 },
79
- byType: {},
80
- };
81
- }
39
+ if (!creations) return EMPTY_STATS;
82
40
  return calculateCreationStats(creations);
83
41
  }, [creations]);
84
42
 
85
- // Filter creations
86
43
  const filtered = useMemo(() => {
87
44
  if (!creations) return [];
88
-
89
- let result = [...creations];
90
-
91
- // Filter by type/category
92
- if (filter.type && filter.type !== "all") {
93
- const filterType = filter.type;
94
-
95
- // Check if it's a category
96
- if (["image", "video", "voice"].includes(filterType)) {
97
- const category = filterType as CreationCategory;
98
- result = result.filter((c) =>
99
- c.type && isTypeInCategory(c.type as CreationTypeId, category)
100
- );
101
- } else {
102
- // It's a specific type
103
- result = result.filter((c) => c.type === filterType);
104
- }
105
- }
106
-
107
- // Filter by status
108
- if (filter.status && filter.status !== "all") {
109
- result = result.filter((c) => c.status === filter.status);
110
- }
111
-
112
- // Filter by search query
113
- if (filter.searchQuery && filter.searchQuery.trim()) {
114
- const query = filter.searchQuery.toLowerCase().trim();
115
- result = result.filter((c) => {
116
- const prompt = c.prompt?.toLowerCase() || "";
117
- const type = c.type?.toLowerCase() || "";
118
- return prompt.includes(query) || type.includes(query);
119
- });
120
- }
121
-
122
- // Filter by date range
123
- if (filter.startDate) {
124
- result = result.filter((c) => {
125
- const createdAt = c.createdAt instanceof Date
126
- ? c.createdAt.getTime()
127
- : (c.createdAt || 0);
128
- return createdAt >= filter.startDate!;
129
- });
130
- }
131
-
132
- if (filter.endDate) {
133
- result = result.filter((c) => {
134
- const createdAt = c.createdAt instanceof Date
135
- ? c.createdAt.getTime()
136
- : (c.createdAt || 0);
137
- return createdAt <= filter.endDate!;
138
- });
139
- }
140
-
141
- // Sort
142
- if (filter.sortField) {
143
- result.sort((a, b) => {
144
- let aVal: string | number | Date | undefined;
145
- let bVal: string | number | Date | undefined;
146
-
147
- switch (filter.sortField) {
148
- case "createdAt":
149
- aVal = a.createdAt;
150
- bVal = b.createdAt;
151
- break;
152
- case "updatedAt":
153
- aVal = a.updatedAt;
154
- bVal = b.updatedAt;
155
- break;
156
- case "type":
157
- aVal = a.type;
158
- bVal = b.type;
159
- break;
160
- case "status":
161
- aVal = a.status;
162
- bVal = b.status;
163
- break;
164
- default:
165
- return 0;
166
- }
167
-
168
- // Convert dates to numbers for comparison
169
- if (aVal instanceof Date) aVal = aVal.getTime();
170
- if (bVal instanceof Date) bVal = bVal.getTime();
171
-
172
- // Handle undefined values
173
- if (aVal === undefined && bVal === undefined) return 0;
174
- if (aVal === undefined) return 1;
175
- if (bVal === undefined) return -1;
176
-
177
- // Compare
178
- if (typeof aVal === "string" && typeof bVal === "string") {
179
- return filter.sortOrder === "desc"
180
- ? bVal.localeCompare(aVal)
181
- : aVal.localeCompare(bVal);
182
- }
183
-
184
- if (typeof aVal === "number" && typeof bVal === "number") {
185
- return filter.sortOrder === "desc" ? bVal - aVal : aVal - bVal;
186
- }
187
-
188
- return 0;
189
- });
190
- }
191
-
192
- // Apply limit
193
- if (filter.limit && filter.limit > 0) {
194
- result = result.slice(0, filter.limit);
195
- }
196
-
197
- return result;
45
+ return applyAllFilters(creations, filter);
198
46
  }, [creations, filter]);
199
47
 
200
- // Media filter options with counts
201
48
  const mediaFilterOptions = useMemo<FilterOption[]>(() => {
202
49
  return MEDIA_FILTER_OPTIONS.map((opt) => ({
203
50
  ...opt,
204
- count: opt.id === "all"
205
- ? stats.total
206
- : stats.byCategory[opt.id as CreationCategory] || 0,
51
+ count:
52
+ opt.id === "all"
53
+ ? stats.total
54
+ : stats.byCategory[opt.id as CreationCategory] || 0,
207
55
  }));
208
56
  }, [stats]);
209
57
 
210
- // Status filter options with counts
211
58
  const statusFilterOptions = useMemo<FilterOption[]>(() => {
212
59
  return STATUS_FILTER_OPTIONS.map((opt) => ({
213
60
  ...opt,
214
- count: opt.id === "all"
215
- ? stats.total
216
- : stats.byStatus[opt.id as CreationStatus] || 0,
61
+ count:
62
+ opt.id === "all"
63
+ ? stats.total
64
+ : stats.byStatus[opt.id as CreationStatus] || 0,
217
65
  }));
218
66
  }, [stats]);
219
67
 
220
- // Actions
221
- const setMediaFilter = useCallback((type: CreationCategory | CreationTypeId) => {
222
- setFilter((prev) => ({ ...prev, type }));
223
- }, []);
68
+ const setMediaFilter = useCallback(
69
+ (type: CreationCategory | CreationTypeId) => {
70
+ setFilter((prev) => ({ ...prev, type }));
71
+ },
72
+ [],
73
+ );
224
74
 
225
75
  const setStatusFilter = useCallback((status: CreationStatus | "all") => {
226
76
  setFilter((prev) => ({ ...prev, status }));
@@ -238,8 +88,8 @@ export function useAdvancedFilter<T extends Creation>({
238
88
  setFilter(DEFAULT_CREATION_FILTER);
239
89
  }, []);
240
90
 
241
- // Derived state
242
- const hasActiveFilters = filter.type !== "all" || filter.status !== "all" || !!filter.searchQuery;
91
+ const hasActiveFilters =
92
+ filter.type !== "all" || filter.status !== "all" || !!filter.searchQuery;
243
93
  const activeMediaFilter = (filter.type as string) || "all";
244
94
  const activeStatusFilter = (filter.status as string) || "all";
245
95
 
@@ -26,7 +26,7 @@ export function parseFlashcardsFromResponse(
26
26
  return rawFlashcards.map((item, index) => mapToGeneratedFlashcard(item, request, index));
27
27
  } catch (error) {
28
28
  if (__DEV__) {
29
- // eslint-disable-next-line no-console
29
+
30
30
  console.error("Failed to parse AI response:", error);
31
31
  }
32
32
  return [];