@primocaredentgroup/elettromedicali 0.1.0 → 0.1.1

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 (150) hide show
  1. package/dist/client/index.d.ts +0 -2
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +2 -28
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +4 -4
  6. package/dist/component/_generated/api.d.ts.map +1 -1
  7. package/dist/component/_generated/component.d.ts +165 -218
  8. package/dist/component/_generated/component.d.ts.map +1 -1
  9. package/dist/component/contracts.d.ts +9 -9
  10. package/dist/component/contracts.d.ts.map +1 -1
  11. package/dist/component/contracts.js +7 -13
  12. package/dist/component/contracts.js.map +1 -1
  13. package/dist/component/crons.d.ts.map +1 -1
  14. package/dist/component/crons.js +1 -2
  15. package/dist/component/crons.js.map +1 -1
  16. package/dist/component/dashboardStats.d.ts +8 -3
  17. package/dist/component/dashboardStats.d.ts.map +1 -1
  18. package/dist/component/dashboardStats.js +24 -39
  19. package/dist/component/dashboardStats.js.map +1 -1
  20. package/dist/component/dashboardStatsCache.d.ts +5 -11
  21. package/dist/component/dashboardStatsCache.d.ts.map +1 -1
  22. package/dist/component/dashboardStatsCache.js +12 -53
  23. package/dist/component/dashboardStatsCache.js.map +1 -1
  24. package/dist/component/deviceCategories.d.ts +22 -15
  25. package/dist/component/deviceCategories.d.ts.map +1 -1
  26. package/dist/component/deviceCategories.js +10 -4
  27. package/dist/component/deviceCategories.js.map +1 -1
  28. package/dist/component/deviceQuestions.d.ts +36 -27
  29. package/dist/component/deviceQuestions.d.ts.map +1 -1
  30. package/dist/component/deviceQuestions.js +22 -5
  31. package/dist/component/deviceQuestions.js.map +1 -1
  32. package/dist/component/deviceRepairHistory.d.ts +3 -3
  33. package/dist/component/deviceRepairHistory.js +1 -1
  34. package/dist/component/deviceRepairHistory.js.map +1 -1
  35. package/dist/component/deviceStatus.d.ts +8 -57
  36. package/dist/component/deviceStatus.d.ts.map +1 -1
  37. package/dist/component/deviceStatus.js +32 -30
  38. package/dist/component/deviceStatus.js.map +1 -1
  39. package/dist/component/devices.d.ts +39 -22
  40. package/dist/component/devices.d.ts.map +1 -1
  41. package/dist/component/devices.js +85 -96
  42. package/dist/component/devices.js.map +1 -1
  43. package/dist/component/emailHelpers.d.ts +10 -3
  44. package/dist/component/emailHelpers.d.ts.map +1 -1
  45. package/dist/component/emailHelpers.js +9 -20
  46. package/dist/component/emailHelpers.js.map +1 -1
  47. package/dist/component/emails.d.ts +5 -5
  48. package/dist/component/emails.js +2 -2
  49. package/dist/component/emails.js.map +1 -1
  50. package/dist/component/http.d.ts.map +1 -1
  51. package/dist/component/http.js +3 -108
  52. package/dist/component/http.js.map +1 -1
  53. package/dist/component/migrationHelpers.d.ts +29 -0
  54. package/dist/component/migrationHelpers.d.ts.map +1 -0
  55. package/dist/component/migrationHelpers.js +84 -0
  56. package/dist/component/migrationHelpers.js.map +1 -0
  57. package/dist/component/roles.d.ts +1 -0
  58. package/dist/component/roles.d.ts.map +1 -1
  59. package/dist/component/roles.js +5 -6
  60. package/dist/component/roles.js.map +1 -1
  61. package/dist/component/schema.d.ts +69 -150
  62. package/dist/component/schema.d.ts.map +1 -1
  63. package/dist/component/schema.js +35 -88
  64. package/dist/component/schema.js.map +1 -1
  65. package/dist/component/slaMonitoring.d.ts +16 -30
  66. package/dist/component/slaMonitoring.d.ts.map +1 -1
  67. package/dist/component/slaMonitoring.js +48 -99
  68. package/dist/component/slaMonitoring.js.map +1 -1
  69. package/dist/component/spareParts.d.ts +11 -48
  70. package/dist/component/spareParts.d.ts.map +1 -1
  71. package/dist/component/spareParts.js +41 -11
  72. package/dist/component/spareParts.js.map +1 -1
  73. package/dist/component/suppliers.d.ts +38 -19
  74. package/dist/component/suppliers.d.ts.map +1 -1
  75. package/dist/component/suppliers.js +63 -44
  76. package/dist/component/suppliers.js.map +1 -1
  77. package/dist/component/ticketComments.d.ts +18 -12
  78. package/dist/component/ticketComments.d.ts.map +1 -1
  79. package/dist/component/ticketComments.js +28 -59
  80. package/dist/component/ticketComments.js.map +1 -1
  81. package/dist/component/ticketDeviceData.d.ts +63 -0
  82. package/dist/component/ticketDeviceData.d.ts.map +1 -0
  83. package/dist/component/ticketDeviceData.js +103 -0
  84. package/dist/component/ticketDeviceData.js.map +1 -0
  85. package/dist/component/ticketExport.d.ts +22 -40
  86. package/dist/component/ticketExport.d.ts.map +1 -1
  87. package/dist/component/ticketExport.js +43 -109
  88. package/dist/component/ticketExport.js.map +1 -1
  89. package/dist/component/ticketHistory.d.ts +4 -4
  90. package/dist/component/ticketHistory.d.ts.map +1 -1
  91. package/dist/component/ticketHistory.js +6 -9
  92. package/dist/component/ticketHistory.js.map +1 -1
  93. package/dist/component/ticketMacros.d.ts +19 -18
  94. package/dist/component/ticketMacros.d.ts.map +1 -1
  95. package/dist/component/ticketMacros.js +24 -30
  96. package/dist/component/ticketMacros.js.map +1 -1
  97. package/dist/component/ticketStatuses.d.ts +1 -0
  98. package/dist/component/ticketStatuses.d.ts.map +1 -1
  99. package/dist/component/ticketStatuses.js +5 -6
  100. package/dist/component/ticketStatuses.js.map +1 -1
  101. package/dist/component/ticketTriggers.d.ts +36 -16
  102. package/dist/component/ticketTriggers.d.ts.map +1 -1
  103. package/dist/component/ticketTriggers.js +115 -153
  104. package/dist/component/ticketTriggers.js.map +1 -1
  105. package/dist/component/userProfiles.d.ts +25 -120
  106. package/dist/component/userProfiles.d.ts.map +1 -1
  107. package/dist/component/userProfiles.js +73 -384
  108. package/dist/component/userProfiles.js.map +1 -1
  109. package/dist/test.d.ts +69 -150
  110. package/dist/test.d.ts.map +1 -1
  111. package/package.json +12 -3
  112. package/src/client/index.ts +2 -30
  113. package/src/component/_generated/api.ts +4 -4
  114. package/src/component/_generated/component.ts +228 -350
  115. package/src/component/contracts.ts +7 -14
  116. package/src/component/crons.ts +2 -7
  117. package/src/component/dashboardStats.ts +24 -41
  118. package/src/component/dashboardStatsCache.ts +12 -61
  119. package/src/component/deviceCategories.ts +12 -4
  120. package/src/component/deviceQuestions.ts +28 -5
  121. package/src/component/deviceRepairHistory.ts +1 -1
  122. package/src/component/deviceStatus.ts +43 -45
  123. package/src/component/devices.ts +87 -106
  124. package/src/component/emailHelpers.ts +9 -19
  125. package/src/component/emails.ts +2 -2
  126. package/src/component/http.ts +3 -108
  127. package/src/component/migrationHelpers.ts +96 -0
  128. package/src/component/roles.ts +5 -6
  129. package/src/component/schema.ts +35 -93
  130. package/src/component/slaMonitoring.ts +52 -107
  131. package/src/component/spareParts.ts +46 -12
  132. package/src/component/suppliers.ts +71 -48
  133. package/src/component/ticketComments.ts +28 -71
  134. package/src/component/ticketDeviceData.ts +113 -0
  135. package/src/component/ticketExport.ts +52 -137
  136. package/src/component/ticketHistory.ts +6 -9
  137. package/src/component/ticketMacros.ts +25 -37
  138. package/src/component/ticketStatuses.ts +5 -6
  139. package/src/component/ticketTriggers.ts +121 -217
  140. package/src/component/userProfiles.ts +67 -451
  141. package/dist/component/clinics.d.ts +0 -103
  142. package/dist/component/clinics.d.ts.map +0 -1
  143. package/dist/component/clinics.js +0 -126
  144. package/dist/component/clinics.js.map +0 -1
  145. package/dist/component/maintenanceTasks.d.ts +0 -733
  146. package/dist/component/maintenanceTasks.d.ts.map +0 -1
  147. package/dist/component/maintenanceTasks.js +0 -937
  148. package/dist/component/maintenanceTasks.js.map +0 -1
  149. package/src/component/clinics.ts +0 -136
  150. package/src/component/maintenanceTasks.ts +0 -1003
@@ -10,21 +10,14 @@ export const listContracts = query({
10
10
  contracts.map(async (contract) => {
11
11
  const supplier = await ctx.db.get(contract.supplierId);
12
12
 
13
- let clinicNames: string[] = [];
14
- if (contract.clinicIds && contract.clinicIds.length > 0) {
15
- const clinics = await Promise.all(
16
- contract.clinicIds.map(id => ctx.db.get(id))
17
- );
18
- clinicNames = clinics.map(c => c?.name || 'Sconosciuta');
19
- } else if (contract.clinicId) {
20
- const clinic = await ctx.db.get(contract.clinicId);
21
- clinicNames = clinic ? [clinic.name] : [];
22
- }
13
+ const clinicNames = contract.clinicIds && contract.clinicIds.length > 0
14
+ ? contract.clinicIds
15
+ : (contract.clinicId ? [contract.clinicId] : ['Tutte le cliniche']);
23
16
 
24
17
  return {
25
18
  ...contract,
26
19
  supplierName: supplier?.name || 'Unknown',
27
- clinicNames: clinicNames.length > 0 ? clinicNames : ['Tutte le cliniche'],
20
+ clinicNames,
28
21
  };
29
22
  })
30
23
  );
@@ -46,7 +39,7 @@ export const getContractsBySupplier = query({
46
39
  });
47
40
 
48
41
  export const getContractsByClinic = query({
49
- args: { clinicId: v.id('clinics') },
42
+ args: { clinicId: v.string() },
50
43
  handler: async (ctx, args) => {
51
44
  const contracts = await ctx.db
52
45
  .query('contracts')
@@ -60,7 +53,7 @@ export const getContractsByClinic = query({
60
53
  export const createContract = mutation({
61
54
  args: {
62
55
  supplierId: v.id('suppliers'),
63
- clinicIds: v.optional(v.array(v.id('clinics'))),
56
+ clinicIds: v.optional(v.array(v.string())),
64
57
  start_date: v.number(),
65
58
  end_date: v.number(),
66
59
  amount: v.number(),
@@ -91,7 +84,7 @@ export const createContract = mutation({
91
84
  export const updateContract = mutation({
92
85
  args: {
93
86
  contractId: v.id('contracts'),
94
- clinicIds: v.optional(v.array(v.id('clinics'))),
87
+ clinicIds: v.optional(v.array(v.string())),
95
88
  start_date: v.optional(v.number()),
96
89
  end_date: v.optional(v.number()),
97
90
  description: v.optional(v.string()),
@@ -3,16 +3,11 @@ import { internal } from './_generated/api';
3
3
 
4
4
  const crons = cronJobs();
5
5
 
6
- crons.cron(
7
- 'check-sla-status',
8
- '0 2 * * *',
9
- internal.slaMonitoring.checkSLAStatus
10
- );
11
-
12
6
  crons.cron(
13
7
  'compute-dashboard-stats',
14
8
  '*/10 * * * *',
15
- internal.dashboardStatsCache.computeAndStoreStats
9
+ internal.dashboardStatsCache.computeAndStoreStats,
10
+ {}
16
11
  );
17
12
 
18
13
  export default crons;
@@ -1,31 +1,28 @@
1
1
  import { query } from './_generated/server';
2
+ import { v } from 'convex/values';
2
3
 
3
4
  export const getAdminDashboardStats = query({
4
- args: {},
5
- handler: async (ctx) => {
5
+ args: {
6
+ totalClinics: v.optional(v.number()),
7
+ ticketStats: v.optional(v.any()),
8
+ },
9
+ handler: async (ctx, args) => {
6
10
  try {
7
11
  const cache = await ctx.db.query('dashboard_stats_cache').first();
8
12
  if (cache && cache.lastUpdated) {
9
13
  const ageMs = Date.now() - cache.lastUpdated;
10
14
  if (ageMs < 15 * 60 * 1000) {
11
15
  return {
12
- totalClinics: cache.totalClinics,
16
+ totalClinics: args.totalClinics ?? cache.totalClinics,
13
17
  totalDevices: cache.totalDevices,
14
18
  totalSuppliers: cache.totalSuppliers,
15
19
  activeContracts: 0,
16
- ticketStats: cache.ticketStats || {},
20
+ ticketStats: args.ticketStats ?? cache.ticketStats ?? {},
17
21
  };
18
22
  }
19
23
  }
20
24
 
21
- const [clinics, suppliers, statuses] = await Promise.all([
22
- ctx.db.query('clinics').take(500),
23
- ctx.db.query('suppliers').take(500),
24
- ctx.db
25
- .query('ticket_statuses')
26
- .withIndex('by_isActive', (q: any) => q.eq('isActive', true))
27
- .collect(),
28
- ]);
25
+ const suppliers = await ctx.db.query('suppliers').take(500);
29
26
 
30
27
  const DEVICE_LIMIT = 300;
31
28
  const deviceStatuses = ['active', 'in_maintenance', 'out_of_service'];
@@ -37,22 +34,12 @@ export const getAdminDashboardStats = query({
37
34
  .take(DEVICE_LIMIT)).length;
38
35
  }
39
36
 
40
- const TICKET_LIMIT = 300;
41
- const ticketStats: any = {};
42
- for (const status of statuses) {
43
- const count = (await ctx.db
44
- .query('maintenance_tasks')
45
- .withIndex('by_status', (q: any) => q.eq('status', status.name))
46
- .take(TICKET_LIMIT)).length;
47
- ticketStats[status.name] = count;
48
- }
49
-
50
37
  return {
51
- totalClinics: clinics.length,
38
+ totalClinics: args.totalClinics ?? 0,
52
39
  totalDevices,
53
40
  totalSuppliers: suppliers.length,
54
41
  activeContracts: 0,
55
- ticketStats,
42
+ ticketStats: args.ticketStats ?? {},
56
43
  };
57
44
  } catch (error) {
58
45
  console.error('Error in getAdminDashboardStats:', error);
@@ -68,41 +55,37 @@ export const getAdminDashboardStats = query({
68
55
  });
69
56
 
70
57
  export const getRecentActivity = query({
71
- args: {},
72
- handler: async (ctx) => {
58
+ args: {
59
+ recentTickets: v.optional(v.array(v.any())),
60
+ },
61
+ handler: async (ctx, args) => {
73
62
  try {
74
- const recentTasks = await ctx.db
75
- .query('maintenance_tasks')
76
- .order('desc')
77
- .take(10);
78
-
63
+ const recentTasks = args.recentTickets || [];
79
64
  const statuses = await ctx.db.query('ticket_statuses').collect();
80
65
  const statusMap = new Map(statuses.map(s => [s.name, s.label]));
81
66
  const activities = [];
82
67
 
83
68
  for (const task of recentTasks) {
84
- const device = task.deviceId ? await ctx.db.get(task.deviceId) : null;
85
- const clinic = task.clinicId ? await ctx.db.get(task.clinicId) : null;
86
69
  const statusLabel = statusMap.get(task.status) || task.status;
70
+ const deviceName = task.deviceName || 'Unknown';
87
71
 
88
72
  let message = '';
89
73
  if (task.status === 'open') {
90
- message = `Nuovo ticket creato per dispositivo ${device?.name || 'Unknown'}`;
74
+ message = `Nuovo ticket creato per dispositivo ${deviceName}`;
91
75
  } else if (task.status === 'completed' || task.status === 'closed') {
92
- message = `Ticket completato per dispositivo ${device?.name || 'Unknown'}`;
76
+ message = `Ticket completato per dispositivo ${deviceName}`;
93
77
  } else if (task.status === 'assigned_to_supplier') {
94
- const supplier = task.supplierId ? await ctx.db.get(task.supplierId) : null;
95
- message = `Ticket assegnato a ${supplier?.name || 'fornitore'}`;
78
+ message = `Ticket assegnato a ${task.supplierName || 'fornitore'}`;
96
79
  } else {
97
- message = `Ticket ${statusLabel} per dispositivo ${device?.name || 'Unknown'}`;
80
+ message = `Ticket ${statusLabel} per dispositivo ${deviceName}`;
98
81
  }
99
82
 
100
83
  activities.push({
101
- id: task._id,
84
+ id: task._id || task.ticketId,
102
85
  type: 'ticket',
103
86
  message,
104
- time: task.created_at,
105
- clinicName: clinic?.name,
87
+ time: task.created_at || task.lastActivityAt,
88
+ clinicId: task.clinicId,
106
89
  });
107
90
  }
108
91
 
@@ -2,33 +2,6 @@ import { v } from 'convex/values';
2
2
  import { internalQuery, internalMutation, internalAction } from './_generated/server';
3
3
  import { internal } from './_generated/api';
4
4
 
5
- const BATCH_SIZE = 50;
6
-
7
- export const countTicketsBatch = internalQuery({
8
- args: {
9
- status: v.string(),
10
- cursor: v.optional(v.number()),
11
- },
12
- handler: async (ctx, args) => {
13
- const q = ctx.db
14
- .query('maintenance_tasks')
15
- .withIndex('by_status_created_at', (q) =>
16
- args.cursor != null
17
- ? q.eq('status', args.status).lt('created_at', args.cursor)
18
- : q.eq('status', args.status)
19
- )
20
- .order('desc')
21
- .take(BATCH_SIZE);
22
-
23
- const batch = await q;
24
- return {
25
- count: batch.length,
26
- hasMore: batch.length === BATCH_SIZE,
27
- lastCursor: batch.length > 0 ? batch[batch.length - 1].created_at : null,
28
- };
29
- },
30
- });
31
-
32
5
  export const countDevicesBatch = internalQuery({
33
6
  args: { status: v.union(v.literal('active'), v.literal('in_maintenance'), v.literal('out_of_service')) },
34
7
  handler: async (ctx, args) => {
@@ -41,63 +14,41 @@ export const countDevicesBatch = internalQuery({
41
14
  });
42
15
 
43
16
  export const computeAndStoreStats = internalAction({
44
- args: {},
45
- handler: async (ctx) => {
17
+ args: {
18
+ ticketStats: v.optional(v.any()),
19
+ totalTickets: v.optional(v.number()),
20
+ },
21
+ handler: async (ctx, args) => {
46
22
  const now = Date.now();
47
23
 
48
- const [clinics, suppliers, statuses] = await Promise.all([
49
- ctx.runQuery(internal.dashboardStatsCache.countTable, { table: 'clinics' }),
50
- ctx.runQuery(internal.dashboardStatsCache.countTable, { table: 'suppliers' }),
51
- ctx.runQuery(internal.dashboardStatsCache.getActiveStatuses, {}),
52
- ]);
24
+ const suppliers = await ctx.runQuery(internal.dashboardStatsCache.countTable, { table: 'suppliers' });
53
25
 
54
26
  const deviceStatuses = ['active', 'in_maintenance', 'out_of_service'] as const;
55
27
  const deviceCountByStatus: Record<string, number> = {};
56
28
  let totalDevices = 0;
57
29
 
58
30
  for (const status of deviceStatuses) {
59
- const r = await ctx.runQuery(internal.dashboardStatsCache.countDevicesBatch, {
60
- status,
61
- });
31
+ const r = await ctx.runQuery(internal.dashboardStatsCache.countDevicesBatch, { status });
62
32
  deviceCountByStatus[status] = r.count;
63
33
  totalDevices += r.count;
64
34
  }
65
35
 
66
- const ticketStats: Record<string, number> = {};
67
- let totalTickets = 0;
68
-
69
- for (const status of statuses) {
70
- let cursor: number | null | undefined = undefined;
71
- let total = 0;
72
- do {
73
- const batchResult: { count: number; hasMore: boolean; lastCursor: number | null } =
74
- await ctx.runQuery(internal.dashboardStatsCache.countTicketsBatch, {
75
- status,
76
- cursor: cursor ?? undefined,
77
- });
78
- total += batchResult.count;
79
- cursor = batchResult.hasMore ? batchResult.lastCursor ?? null : null;
80
- } while (cursor != null);
81
- ticketStats[status] = total;
82
- totalTickets += total;
83
- }
84
-
85
36
  await ctx.runMutation(internal.dashboardStatsCache.writeCache, {
86
37
  lastUpdated: now,
87
- totalClinics: clinics,
38
+ totalClinics: 0,
88
39
  totalSuppliers: suppliers,
89
40
  totalDevices,
90
41
  deviceCountByStatus,
91
- ticketStats,
92
- totalTickets,
42
+ ticketStats: args.ticketStats ?? {},
43
+ totalTickets: args.totalTickets ?? 0,
93
44
  });
94
45
 
95
- return { ok: true, totalTickets, totalDevices };
46
+ return { ok: true, totalDevices };
96
47
  },
97
48
  });
98
49
 
99
50
  export const countTable = internalQuery({
100
- args: { table: v.union(v.literal('clinics'), v.literal('suppliers')) },
51
+ args: { table: v.union(v.literal('suppliers')) },
101
52
  handler: async (ctx, args) => {
102
53
  const r = await ctx.db.query(args.table).take(2000);
103
54
  return r.length;
@@ -84,14 +84,18 @@ export const importCategories = mutation({
84
84
  // Get all categories
85
85
  export const list = query({
86
86
  args: {
87
+ page: v.optional(v.number()),
88
+ pageSize: v.optional(v.number()),
87
89
  search: v.optional(v.string()),
88
- noteFilter: v.optional(v.string()), // null, "1", "2", "3"
90
+ noteFilter: v.optional(v.string()),
89
91
  typeFilter: v.optional(v.string()),
90
92
  },
91
93
  handler: async (ctx, args) => {
94
+ const page = args.page || 1;
95
+ const pageSize = args.pageSize || 20;
96
+
92
97
  let categories = await ctx.db.query('device_categories').collect();
93
98
 
94
- // Apply filters
95
99
  if (args.search) {
96
100
  const searchLower = args.search.toLowerCase();
97
101
  categories = categories.filter((c) =>
@@ -112,10 +116,14 @@ export const list = query({
112
116
  categories = categories.filter((c) => c.type === args.typeFilter);
113
117
  }
114
118
 
115
- // Sort by name
116
119
  categories.sort((a, b) => a.name.localeCompare(b.name));
117
120
 
118
- return categories;
121
+ const totalCount = categories.length;
122
+ const totalPages = Math.ceil(totalCount / pageSize);
123
+ const startIndex = (page - 1) * pageSize;
124
+ const pageCategories = categories.slice(startIndex, startIndex + pageSize);
125
+
126
+ return { page: pageCategories, totalCount, totalPages, currentPage: page };
119
127
  },
120
128
  });
121
129
 
@@ -17,18 +17,25 @@ export const listQuestionsByDevice = query({
17
17
  });
18
18
 
19
19
  export const listAllQuestions = query({
20
- args: {},
21
- handler: async (ctx) => {
20
+ args: {
21
+ page: v.optional(v.number()),
22
+ pageSize: v.optional(v.number()),
23
+ search: v.optional(v.string()),
24
+ },
25
+ handler: async (ctx, args) => {
26
+ const page = args.page || 1;
27
+ const pageSize = args.pageSize || 20;
28
+
22
29
  try {
23
30
  const questions = await ctx.db
24
31
  .query('device_questions')
25
32
  .collect();
26
33
 
27
34
  if (questions.length === 0) {
28
- return [];
35
+ return { page: [], totalCount: 0, totalPages: 0, currentPage: page };
29
36
  }
30
37
 
31
- const questionsWithDevices = await Promise.all(
38
+ let questionsWithDevices = await Promise.all(
32
39
  questions.map(async (question) => {
33
40
  const device = await ctx.db.get(question.deviceId);
34
41
  return {
@@ -39,12 +46,28 @@ export const listAllQuestions = query({
39
46
  })
40
47
  );
41
48
 
42
- return questionsWithDevices.sort((a, b) => {
49
+ questionsWithDevices.sort((a, b) => {
43
50
  if (a.deviceName !== b.deviceName) {
44
51
  return a.deviceName.localeCompare(b.deviceName);
45
52
  }
46
53
  return a.order - b.order;
47
54
  });
55
+
56
+ if (args.search) {
57
+ const s = args.search.toLowerCase();
58
+ questionsWithDevices = questionsWithDevices.filter((q) =>
59
+ q.question?.toLowerCase().includes(s) ||
60
+ q.deviceName?.toLowerCase().includes(s) ||
61
+ q.deviceCategory?.toLowerCase().includes(s)
62
+ );
63
+ }
64
+
65
+ const totalCount = questionsWithDevices.length;
66
+ const totalPages = Math.ceil(totalCount / pageSize);
67
+ const startIndex = (page - 1) * pageSize;
68
+ const pageQuestions = questionsWithDevices.slice(startIndex, startIndex + pageSize);
69
+
70
+ return { page: pageQuestions, totalCount, totalPages, currentPage: page };
48
71
  } catch (error: any) {
49
72
  console.error('Error in listAllQuestions:', error);
50
73
  throw error;
@@ -18,7 +18,7 @@ export const getRepairHistory = query({
18
18
  export const sendToRepair = mutation({
19
19
  args: {
20
20
  deviceId: v.id('devices'),
21
- ticketId: v.optional(v.id('maintenance_tasks')),
21
+ ticketId: v.optional(v.string()),
22
22
  notes: v.optional(v.string()),
23
23
  createdBy: v.string(),
24
24
  },
@@ -2,35 +2,31 @@ import { query } from './_generated/server';
2
2
  import { v } from 'convex/values';
3
3
 
4
4
  export const computeDeviceStatus = query({
5
- args: { deviceId: v.id('devices') },
6
- handler: async (ctx, { deviceId }) => {
7
- const tickets = await ctx.db
8
- .query('maintenance_tasks')
9
- .withIndex('by_deviceId', (q) => q.eq('deviceId', deviceId))
10
- .take(200);
5
+ args: {
6
+ deviceId: v.id('devices'),
7
+ tickets: v.optional(v.array(v.object({
8
+ status: v.string(),
9
+ description: v.optional(v.string()),
10
+ created_at: v.optional(v.number()),
11
+ updated_at: v.optional(v.number()),
12
+ }))),
13
+ },
14
+ handler: async (ctx, args) => {
15
+ const tickets = args.tickets || [];
11
16
 
12
- if (tickets.length === 0) {
13
- return 'in_uso';
14
- }
17
+ if (tickets.length === 0) return 'in_uso';
15
18
 
16
19
  const smaltito = tickets.find(
17
- (t) =>
18
- t.status === 'smaltito' &&
19
- t.updated_at &&
20
- t.created_at < t.updated_at
20
+ (t) => t.status === 'smaltito' && t.updated_at && t.created_at && t.created_at < t.updated_at
21
21
  );
22
- if (smaltito) {
23
- return 'smaltito';
24
- }
22
+ if (smaltito) return 'smaltito';
25
23
 
26
24
  const raee = tickets.find(
27
25
  (t) =>
28
26
  t.description?.toLowerCase().includes('raee') ||
29
27
  t.description?.toLowerCase().includes('smaltimento')
30
28
  );
31
- if (raee && raee.status !== 'closed' && raee.status !== 'completed') {
32
- return 'da_smaltire';
33
- }
29
+ if (raee && raee.status !== 'closed' && raee.status !== 'completed') return 'da_smaltire';
34
30
 
35
31
  const openTickets = tickets.filter(
36
32
  (t) =>
@@ -40,40 +36,42 @@ export const computeDeviceStatus = query({
40
36
  t.status === 'pending'
41
37
  );
42
38
 
43
- if (openTickets.length > 0) {
44
- return 'guasto';
45
- }
46
-
39
+ if (openTickets.length > 0) return 'guasto';
47
40
  return 'in_uso';
48
41
  },
49
42
  });
50
43
 
51
44
  export const getDeviceTicketHistory = query({
52
- args: { deviceId: v.id('devices') },
53
- handler: async (ctx, { deviceId }) => {
54
- const tickets = await ctx.db
55
- .query('maintenance_tasks')
56
- .withIndex('by_deviceId', (q) => q.eq('deviceId', deviceId))
57
- .order('desc')
58
- .take(50);
45
+ args: {
46
+ deviceId: v.id('devices'),
47
+ tickets: v.optional(v.array(v.any())),
48
+ },
49
+ handler: async (ctx, args) => {
50
+ const tickets = args.tickets || [];
51
+
52
+ const deviceDataEntries = await ctx.db
53
+ .query('ticket_device_data')
54
+ .withIndex('by_deviceId', (q) => q.eq('deviceId', args.deviceId))
55
+ .collect();
56
+
57
+ const ticketIdSet = new Set(deviceDataEntries.map(d => d.ticketId));
59
58
 
60
- const enrichedTickets = await Promise.all(
61
- tickets.map(async (ticket) => {
62
- const clinic = ticket.clinicId
63
- ? await ctx.db.get(ticket.clinicId)
64
- : null;
65
- const supplier = ticket.supplierId
66
- ? await ctx.db.get(ticket.supplierId)
67
- : null;
59
+ const enriched = await Promise.all(
60
+ tickets
61
+ .filter((t: any) => ticketIdSet.has(t._id?.toString() || t.ticketId))
62
+ .map(async (ticket: any) => {
63
+ const supplier = ticket.supplierId
64
+ ? await ctx.db.get(ticket.supplierId)
65
+ : null;
68
66
 
69
- return {
70
- ...ticket,
71
- clinic,
72
- supplier,
73
- };
74
- })
67
+ return {
68
+ ...ticket,
69
+ clinic: null,
70
+ supplier,
71
+ };
72
+ })
75
73
  );
76
74
 
77
- return enrichedTickets;
75
+ return enriched;
78
76
  },
79
77
  });