@open-mercato/core 0.6.2-develop.3446.1.bd060c6017 → 0.6.2-develop.3467.1.2a1818709d
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/route.js +3 -6
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +2 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/commands/users.js +5 -8
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +1 -1
- package/dist/modules/auth/frontend/login.js +35 -0
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/lib/displayName.js +10 -0
- package/dist/modules/auth/lib/displayName.js.map +7 -0
- package/dist/modules/business_rules/lib/rule-evaluator.js +3 -3
- package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +4 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +8 -6
- package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +2 -2
- package/dist/modules/messages/components/messageListLabels.js +14 -0
- package/dist/modules/messages/components/messageListLabels.js.map +7 -0
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js +47 -23
- package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/route.ts +3 -6
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -1
- package/src/modules/auth/backend/users/create/page.tsx +2 -1
- package/src/modules/auth/commands/users.ts +5 -8
- package/src/modules/auth/data/entities.ts +1 -1
- package/src/modules/auth/frontend/login.tsx +40 -0
- package/src/modules/auth/lib/displayName.ts +6 -0
- package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +5 -2
- package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
- package/src/modules/messages/components/messageListLabels.ts +29 -0
- package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
- package/src/modules/messages/i18n/de.json +2 -0
- package/src/modules/messages/i18n/en.json +2 -0
- package/src/modules/messages/i18n/es.json +2 -0
- package/src/modules/messages/i18n/pl.json +2 -0
|
@@ -26,7 +26,7 @@ async function evaluate(rules, data, context) {
|
|
|
26
26
|
failedRules.push(rule);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
const evaluationTime = Date.now() - startTime;
|
|
29
|
+
const evaluationTime = Math.max(0, Date.now() - startTime);
|
|
30
30
|
const conditionsPassed = matchedRules.length > 0;
|
|
31
31
|
return {
|
|
32
32
|
conditionsPassed,
|
|
@@ -70,7 +70,7 @@ async function evaluateSingleRule(rule, data, context) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context);
|
|
73
|
-
const evaluationTime = Date.now() - startTime;
|
|
73
|
+
const evaluationTime = Math.max(0, Date.now() - startTime);
|
|
74
74
|
console.log("[RULE EVAL] Final result:", {
|
|
75
75
|
ruleId: rule.ruleId,
|
|
76
76
|
conditionsPassed: conditionsPassed ? "\u2713 PASS" : "\u2717 FAIL",
|
|
@@ -84,7 +84,7 @@ async function evaluateSingleRule(rule, data, context) {
|
|
|
84
84
|
evaluationTime
|
|
85
85
|
};
|
|
86
86
|
} catch (error) {
|
|
87
|
-
const evaluationTime = Date.now() - startTime;
|
|
87
|
+
const evaluationTime = Math.max(0, Date.now() - startTime);
|
|
88
88
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
89
|
console.log("[RULE EVAL] \u2717 ERROR:", errorMessage);
|
|
90
90
|
console.log("[RULE EVAL] ========================================");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/business_rules/lib/rule-evaluator.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BusinessRule } from '../data/entities'\nimport { evaluateExpression, type EvaluationContext, type ConditionExpression } from './expression-evaluator'\n\n/**\n * Rule evaluation context\n */\nexport interface RuleEvaluationContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n [key: string]: any\n}\n\n/**\n * Rule evaluation result (for evaluating multiple rules)\n */\nexport interface RuleEvaluationResult {\n conditionsPassed: boolean // At least one rule matched\n evaluationCompleted: boolean // All rules evaluated without critical errors\n matchedRules: BusinessRule[]\n failedRules: BusinessRule[]\n evaluationTime: number\n errors?: string[]\n}\n\n/**\n * Single rule evaluation result\n */\nexport interface SingleRuleResult {\n rule: BusinessRule\n conditionsPassed: boolean // Logical result: did conditions evaluate to true?\n evaluationCompleted: boolean // Technical success: did evaluation finish without errors?\n evaluationTime: number\n error?: string\n}\n\n/**\n * Evaluate multiple rules and return aggregated results\n */\nexport async function evaluate(\n rules: BusinessRule[],\n data: any,\n context: RuleEvaluationContext\n): Promise<RuleEvaluationResult> {\n const startTime = Date.now()\n const matchedRules: BusinessRule[] = []\n const failedRules: BusinessRule[] = []\n const errors: string[] = []\n let anyEvaluationCompleted = false\n\n // Sort rules by priority (higher priority first)\n const sortedRules = sortRulesByPriority(rules)\n\n // Evaluate each rule\n for (const rule of sortedRules) {\n try {\n const result = await evaluateSingleRule(rule, data, context)\n\n // Track if any rule completed evaluation\n if (result.evaluationCompleted) {\n anyEvaluationCompleted = true\n }\n\n if (result.evaluationCompleted && result.conditionsPassed) {\n matchedRules.push(rule)\n } else {\n failedRules.push(rule)\n }\n\n if (result.error) {\n errors.push(`Rule ${rule.ruleId}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Rule ${rule.ruleId}: ${errorMessage}`)\n failedRules.push(rule)\n }\n }\n\n const evaluationTime = Date.now() - startTime\n\n // Determine if conditions were met (at least one rule matched)\n const conditionsPassed = matchedRules.length > 0\n\n return {\n conditionsPassed,\n evaluationCompleted: anyEvaluationCompleted,\n matchedRules,\n failedRules,\n evaluationTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Evaluate a single business rule\n */\nexport async function evaluateSingleRule(\n rule: BusinessRule,\n data: any,\n context: RuleEvaluationContext\n): Promise<SingleRuleResult> {\n const startTime = Date.now()\n\n console.log('[RULE EVAL] ========================================')\n console.log('[RULE EVAL] Evaluating rule:', {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n ruleType: rule.ruleType,\n entityType: rule.entityType,\n eventType: rule.eventType,\n })\n console.log('[RULE EVAL] Conditions:', JSON.stringify(rule.conditionExpression, null, 2))\n\n try {\n // Check if rule is enabled\n if (!rule.enabled) {\n console.log('[RULE EVAL] \u2717 Rule is disabled')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is disabled',\n }\n }\n\n // Check effective date range\n if (!isRuleEffective(rule)) {\n console.log('[RULE EVAL] \u2717 Rule is not effective (outside date range)')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is not effective (outside date range)',\n }\n }\n\n // Evaluate conditions\n const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)\n\n const evaluationTime = Date.now() - startTime\n\n console.log('[RULE EVAL] Final result:', {\n ruleId: rule.ruleId,\n conditionsPassed: conditionsPassed ? '\u2713 PASS' : '\u2717 FAIL',\n evaluationTime: `${evaluationTime}ms`,\n })\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed,\n evaluationCompleted: true,\n evaluationTime,\n }\n } catch (error) {\n const evaluationTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n console.log('[RULE EVAL] \u2717 ERROR:', errorMessage)\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime,\n error: errorMessage,\n }\n }\n}\n\n/**\n * Evaluate rule conditions (delegates to expression evaluator)\n */\nexport async function evaluateConditions(\n conditions: any,\n data: any,\n context: RuleEvaluationContext\n): Promise<boolean> {\n if (!conditions) {\n return true\n }\n\n try {\n const expression = conditions as ConditionExpression\n return evaluateExpression(expression, data, context)\n } catch (error) {\n // If evaluation fails, the rule fails\n throw new Error(`Condition evaluation failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Sort rules by priority (higher priority first, then by ID for stability)\n */\nexport function sortRulesByPriority(rules: BusinessRule[]): BusinessRule[] {\n return [...rules].sort((a, b) => {\n if (b.priority !== a.priority) {\n return b.priority - a.priority\n }\n\n // If priority is the same, sort by ruleId for stability\n return a.ruleId.localeCompare(b.ruleId)\n })\n}\n\n/**\n * Check if rule is within its effective date range\n */\nfunction isRuleEffective(rule: BusinessRule): boolean {\n const now = new Date()\n\n // Check effectiveFrom\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n\n // Check effectiveTo\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n\n return true\n}\n\n/**\n * Filter rules by entity type and event type\n */\nexport function filterRulesByContext(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n return rules.filter((rule) => {\n // Filter by entity type\n if (entityType && rule.entityType !== entityType) {\n return false\n }\n\n // Filter by event type (if rule specifies one)\n if (eventType && rule.eventType && rule.eventType !== eventType) {\n return false\n }\n\n return true\n })\n}\n\n/**\n * Get applicable rules for a context (filtered, sorted, and enabled)\n */\nexport function getApplicableRules(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n // Filter by context\n const filtered = filterRulesByContext(rules, entityType, eventType)\n\n // Filter by enabled status\n const enabled = filtered.filter((rule) => rule.enabled)\n\n // Filter by effective date\n const effective = enabled.filter((rule) => isRuleEffective(rule))\n\n // Sort by priority\n return sortRulesByPriority(effective)\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA4E;AAsCrF,eAAsB,SACpB,OACA,MACA,SAC+B;AAC/B,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,eAA+B,CAAC;AACtC,QAAM,cAA8B,CAAC;AACrC,QAAM,SAAmB,CAAC;AAC1B,MAAI,yBAAyB;AAG7B,QAAM,cAAc,oBAAoB,KAAK;AAG7C,aAAW,QAAQ,aAAa;AAC9B,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,MAAM,MAAM,OAAO;AAG3D,UAAI,OAAO,qBAAqB;AAC9B,iCAAyB;AAAA,MAC3B;AAEA,UAAI,OAAO,uBAAuB,OAAO,kBAAkB;AACzD,qBAAa,KAAK,IAAI;AAAA,MACxB,OAAO;AACL,oBAAY,KAAK,IAAI;AAAA,MACvB;AAEA,UAAI,OAAO,OAAO;AAChB,eAAO,KAAK,QAAQ,KAAK,MAAM,KAAK,OAAO,KAAK,EAAE;AAAA,MACpD;AAAA,IACF,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,aAAO,KAAK,QAAQ,KAAK,MAAM,KAAK,YAAY,EAAE;AAClD,kBAAY,KAAK,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,iBAAiB,KAAK,IAAI,IAAI;
|
|
4
|
+
"sourcesContent": ["import type { BusinessRule } from '../data/entities'\nimport { evaluateExpression, type EvaluationContext, type ConditionExpression } from './expression-evaluator'\n\n/**\n * Rule evaluation context\n */\nexport interface RuleEvaluationContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n [key: string]: any\n}\n\n/**\n * Rule evaluation result (for evaluating multiple rules)\n */\nexport interface RuleEvaluationResult {\n conditionsPassed: boolean // At least one rule matched\n evaluationCompleted: boolean // All rules evaluated without critical errors\n matchedRules: BusinessRule[]\n failedRules: BusinessRule[]\n evaluationTime: number\n errors?: string[]\n}\n\n/**\n * Single rule evaluation result\n */\nexport interface SingleRuleResult {\n rule: BusinessRule\n conditionsPassed: boolean // Logical result: did conditions evaluate to true?\n evaluationCompleted: boolean // Technical success: did evaluation finish without errors?\n evaluationTime: number\n error?: string\n}\n\n/**\n * Evaluate multiple rules and return aggregated results\n */\nexport async function evaluate(\n rules: BusinessRule[],\n data: any,\n context: RuleEvaluationContext\n): Promise<RuleEvaluationResult> {\n const startTime = Date.now()\n const matchedRules: BusinessRule[] = []\n const failedRules: BusinessRule[] = []\n const errors: string[] = []\n let anyEvaluationCompleted = false\n\n // Sort rules by priority (higher priority first)\n const sortedRules = sortRulesByPriority(rules)\n\n // Evaluate each rule\n for (const rule of sortedRules) {\n try {\n const result = await evaluateSingleRule(rule, data, context)\n\n // Track if any rule completed evaluation\n if (result.evaluationCompleted) {\n anyEvaluationCompleted = true\n }\n\n if (result.evaluationCompleted && result.conditionsPassed) {\n matchedRules.push(rule)\n } else {\n failedRules.push(rule)\n }\n\n if (result.error) {\n errors.push(`Rule ${rule.ruleId}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Rule ${rule.ruleId}: ${errorMessage}`)\n failedRules.push(rule)\n }\n }\n\n const evaluationTime = Math.max(0, Date.now() - startTime)\n\n // Determine if conditions were met (at least one rule matched)\n const conditionsPassed = matchedRules.length > 0\n\n return {\n conditionsPassed,\n evaluationCompleted: anyEvaluationCompleted,\n matchedRules,\n failedRules,\n evaluationTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Evaluate a single business rule\n */\nexport async function evaluateSingleRule(\n rule: BusinessRule,\n data: any,\n context: RuleEvaluationContext\n): Promise<SingleRuleResult> {\n const startTime = Date.now()\n\n console.log('[RULE EVAL] ========================================')\n console.log('[RULE EVAL] Evaluating rule:', {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n ruleType: rule.ruleType,\n entityType: rule.entityType,\n eventType: rule.eventType,\n })\n console.log('[RULE EVAL] Conditions:', JSON.stringify(rule.conditionExpression, null, 2))\n\n try {\n // Check if rule is enabled\n if (!rule.enabled) {\n console.log('[RULE EVAL] \u2717 Rule is disabled')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is disabled',\n }\n }\n\n // Check effective date range\n if (!isRuleEffective(rule)) {\n console.log('[RULE EVAL] \u2717 Rule is not effective (outside date range)')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is not effective (outside date range)',\n }\n }\n\n // Evaluate conditions\n const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)\n\n const evaluationTime = Math.max(0, Date.now() - startTime)\n\n console.log('[RULE EVAL] Final result:', {\n ruleId: rule.ruleId,\n conditionsPassed: conditionsPassed ? '\u2713 PASS' : '\u2717 FAIL',\n evaluationTime: `${evaluationTime}ms`,\n })\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed,\n evaluationCompleted: true,\n evaluationTime,\n }\n } catch (error) {\n const evaluationTime = Math.max(0, Date.now() - startTime)\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n console.log('[RULE EVAL] \u2717 ERROR:', errorMessage)\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime,\n error: errorMessage,\n }\n }\n}\n\n/**\n * Evaluate rule conditions (delegates to expression evaluator)\n */\nexport async function evaluateConditions(\n conditions: any,\n data: any,\n context: RuleEvaluationContext\n): Promise<boolean> {\n if (!conditions) {\n return true\n }\n\n try {\n const expression = conditions as ConditionExpression\n return evaluateExpression(expression, data, context)\n } catch (error) {\n // If evaluation fails, the rule fails\n throw new Error(`Condition evaluation failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Sort rules by priority (higher priority first, then by ID for stability)\n */\nexport function sortRulesByPriority(rules: BusinessRule[]): BusinessRule[] {\n return [...rules].sort((a, b) => {\n if (b.priority !== a.priority) {\n return b.priority - a.priority\n }\n\n // If priority is the same, sort by ruleId for stability\n return a.ruleId.localeCompare(b.ruleId)\n })\n}\n\n/**\n * Check if rule is within its effective date range\n */\nfunction isRuleEffective(rule: BusinessRule): boolean {\n const now = new Date()\n\n // Check effectiveFrom\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n\n // Check effectiveTo\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n\n return true\n}\n\n/**\n * Filter rules by entity type and event type\n */\nexport function filterRulesByContext(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n return rules.filter((rule) => {\n // Filter by entity type\n if (entityType && rule.entityType !== entityType) {\n return false\n }\n\n // Filter by event type (if rule specifies one)\n if (eventType && rule.eventType && rule.eventType !== eventType) {\n return false\n }\n\n return true\n })\n}\n\n/**\n * Get applicable rules for a context (filtered, sorted, and enabled)\n */\nexport function getApplicableRules(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n // Filter by context\n const filtered = filterRulesByContext(rules, entityType, eventType)\n\n // Filter by enabled status\n const enabled = filtered.filter((rule) => rule.enabled)\n\n // Filter by effective date\n const effective = enabled.filter((rule) => isRuleEffective(rule))\n\n // Sort by priority\n return sortRulesByPriority(effective)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,6 +14,7 @@ import { useAppEvent } from "@open-mercato/ui/backend/injection/useAppEvent";
|
|
|
14
14
|
import { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from "lucide-react";
|
|
15
15
|
import { getMessageUiComponentRegistry } from "./utils/typeUiRegistry.js";
|
|
16
16
|
import { DefaultMessageListItem } from "./defaults/DefaultMessageListItem.js";
|
|
17
|
+
import { getMessageListParticipantLabel } from "./messageListLabels.js";
|
|
17
18
|
import { toErrorMessage } from "./message-detail/utils.js";
|
|
18
19
|
import { useMessagesInboxBulkActions } from "./useMessagesInboxBulkActions.js";
|
|
19
20
|
function MessagesInboxPageClient() {
|
|
@@ -243,11 +244,12 @@ function MessagesInboxPageClient() {
|
|
|
243
244
|
bodyFormat: "text",
|
|
244
245
|
priority: item.priority ?? "normal",
|
|
245
246
|
sentAt: item.sentAt ? new Date(item.sentAt) : null,
|
|
246
|
-
senderName: item
|
|
247
|
+
senderName: getMessageListParticipantLabel(item, folder, t),
|
|
247
248
|
hasObjects: item.hasObjects,
|
|
248
249
|
objectCount: item.objectCount,
|
|
249
250
|
hasAttachments: item.hasAttachments,
|
|
250
251
|
attachmentCount: item.attachmentCount,
|
|
252
|
+
recipientCount: item.recipientCount ?? 0,
|
|
251
253
|
hasActions: item.hasActions,
|
|
252
254
|
actionTaken: item.actionTaken ?? null,
|
|
253
255
|
unread: item.status === "unread"
|
|
@@ -257,7 +259,7 @@ function MessagesInboxPageClient() {
|
|
|
257
259
|
);
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
|
-
], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t]);
|
|
262
|
+
], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t]);
|
|
261
263
|
const folderOptions = React.useMemo(() => [
|
|
262
264
|
{ id: "inbox", label: t("messages.folder.inbox", "Inbox"), icon: Inbox },
|
|
263
265
|
{ id: "sent", label: t("messages.folder.sent", "Sent"), icon: Send },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/MessagesInboxPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''\n const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''\n const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''\n const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''\n const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''\n const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''\n const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (since) params.set('since', since)\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(`/api/auth/users?${params.toString()}`)\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n return items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n }, [])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }],\n loadOptions: loadSenderOptions,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),\n },\n ]\n }, [loadSenderOptions, messageTypesQuery.data, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: item.senderName || item.senderEmail || item.senderUserId,\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAuTU,cA2GM,YA3GN;AArTV,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,UAAU,sBAAsB;AAEzC,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAC5C,SAAS,iBAAiB;AAE1B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,SAAS,SAAS,aAAa,aAAa,OAAO,QAAQ,YAAY;AACvE,SAAS,qCAAqC;AAC9C,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,mCAAuD;AA8CzD,SAAS,0BAA0B;AACxC,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,cAAc,eAAe;AACnC,QAAM,eAAe,4BAA4B;AAEjD,QAAM,+BAA+B,MAAM,YAAY,MAAM;AAC3D,SAAK,YAAY,kBAAkB,EAAE,UAAU,CAAC,YAAY,MAAM,EAAE,CAAC;AAAA,EACvE,GAAG,CAAC,WAAW,CAAC;AAEhB,cAAY,sBAAsB,8BAA8B,CAAC,4BAA4B,CAAC;AAE9F,cAAY,yBAAyB,8BAA8B,CAAC,4BAA4B,CAAC;AAEjG,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAwB,OAAO;AACjE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,CAAC,CAAC;AACvE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,WAAW;AACjB,QAAM,gBAAgB,MAAM,OAA8B,IAAI;AAC9D,QAAM,oBAAoB,MAAM,QAAQ,MAAM,8BAA8B,GAAG,CAAC,CAAC;AACjF,QAAM,EAAE,aAAa,mBAAmB,kBAAkB,qBAAqB,IAAI,4BAA6C;AAAA,IAC9H;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,YAAY,SAAS;AAAA,IACzB,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,UAAU,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,SAAS,YAAY;AACnB,YAAM,SAAS,IAAI,gBAAgB;AACnC,aAAO,IAAI,UAAU,MAAM;AAC3B,aAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,aAAO,IAAI,YAAY,OAAO,QAAQ,CAAC;AAEvC,UAAI,OAAO,KAAK,GAAG;AACjB,eAAO,IAAI,UAAU,OAAO,KAAK,CAAC;AAAA,MACpC;AAEA,YAAM,SAAS,OAAO,aAAa,WAAW,WAAW,aAAa,OAAO,KAAK,IAAI;AACtF,YAAM,OAAO,OAAO,aAAa,SAAS,WAAW,aAAa,KAAK,KAAK,IAAI;AAChF,YAAM,aAAa,OAAO,aAAa,eAAe,WAAW,aAAa,WAAW,KAAK,IAAI;AAClG,YAAM,iBAAiB,OAAO,aAAa,mBAAmB,WAAW,aAAa,eAAe,KAAK,IAAI;AAC9G,YAAM,aAAa,OAAO,aAAa,eAAe,WAAW,aAAa,WAAW,KAAK,IAAI;AAClG,YAAM,WAAW,OAAO,aAAa,aAAa,WAAW,aAAa,SAAS,KAAK,IAAI;AAC5F,YAAM,QAAQ,OAAO,aAAa,UAAU,WAAW,aAAa,MAAM,KAAK,IAAI;AAEnF,UAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,UAAI,KAAM,QAAO,IAAI,QAAQ,IAAI;AACjC,UAAI,WAAY,QAAO,IAAI,cAAc,UAAU;AACnD,UAAI,eAAgB,QAAO,IAAI,kBAAkB,cAAc;AAC/D,UAAI,WAAY,QAAO,IAAI,cAAc,UAAU;AACnD,UAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,UAAI,MAAO,QAAO,IAAI,SAAS,KAAK;AAEpC,YAAM,OAAO,MAAM,QAA6B,iBAAiB,OAAO,SAAS,CAAC,EAAE;AACpF,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,IAAI;AAAA,UACR,eAAe,KAAK,MAAM,KACvB,EAAE,kCAAkC,0BAA0B;AAAA,QACnE;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAAA,QACvE,OAAO,OAAO,KAAK,QAAQ,SAAS,CAAC;AAAA,QACrC,MAAM,OAAO,KAAK,QAAQ,QAAQ,IAAI;AAAA,QACtC,UAAU,OAAO,KAAK,QAAQ,YAAY,QAAQ;AAAA,QAClD,YAAY,OAAO,KAAK,QAAQ,cAAc,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,oBAAoB,SAAS;AAAA,IACjC,UAAU,CAAC,YAAY,SAAS,YAAY;AAAA,IAC5C,SAAS,YAAY;AACnB,YAAM,OAAO,MAAM,QAAuC,qBAAqB;AAC/E,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,IAAI;AAAA,UACR,eAAe,KAAK,MAAM,KACvB,EAAE,mCAAmC,+BAA+B;AAAA,QACzE;AAAA,MACF;AACA,aAAO,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU,MAAO;AACtB;AAAA,MACE,UAAU,iBAAiB,QACvB,UAAU,MAAM,UAChB,EAAE,kCAAkC,0BAA0B;AAAA,MAClE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAU,OAAO,CAAC,CAAC;AAEvB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAkB,MAAO;AAC9B;AAAA,MACE,kBAAkB,iBAAiB,QAC/B,kBAAkB,MAAM,UACxB,EAAE,mCAAmC,+BAA+B;AAAA,MACxE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,kBAAkB,OAAO,CAAC,CAAC;AAE/B,QAAM,sBAAsB,MAAM,QAAQ,MAAM;AAC9C,UAAM,MAA8B,CAAC;AACrC,eAAW,QAAQ,kBAAkB,QAAQ,CAAC,GAAG;AAC/C,UAAI,KAAK,IAAI,IAAI,EAAE,KAAK,UAAU,KAAK,IAAI;AAAA,IAC7C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,kBAAkB,MAAM,CAAC,CAAC;AAE9B,QAAM,oBAAoB,MAAM,YAAY,OAAO,UAAmB;AACpE,UAAM,SAAS,IAAI,gBAAgB;AACnC,WAAO,IAAI,QAAQ,GAAG;AACtB,WAAO,IAAI,YAAY,IAAI;AAC3B,QAAI,SAAS,MAAM,KAAK,EAAE,SAAS,GAAG;AACpC,aAAO,IAAI,UAAU,MAAM,KAAK,CAAC;AAAA,IACnC;AAEA,UAAM,OAAO,MAAM,QAAoC,mBAAmB,OAAO,SAAS,CAAC,EAAE;AAC7F,QAAI,CAAC,KAAK,GAAI,QAAO,CAAC;AAEtB,UAAM,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAC9E,WAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,UAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,KAAK,EAAE,WAAW,EAAG,QAAO,CAAC;AACjF,YAAM,OAAO,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,KAAK,EAAE,SAAS,IAAI,KAAK,KAAK,KAAK,IAAI;AAC/F,YAAM,QAAQ,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,IAAI,KAAK,MAAM,KAAK,IAAI;AACnG,YAAM,QAAQ,QAAQ,SAAS,KAAK;AACpC,aAAO,CAAC;AAAA,QACN,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,aAAa,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAClD,CAAC;AAAA,IACH,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,6BAA6B,MAAM,QAAQ,MAAM;AACrD,UAAM,MAAqC,CAAC;AAC5C,eAAW,QAAQ,kBAAkB,QAAQ,CAAC,GAAG;AAC/C,UAAI,KAAK,IAAI,IAAI,KAAK,IAAI,qBAAqB;AAAA,IACjD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,kBAAkB,IAAI,CAAC;AAE3B,QAAM,UAAU,MAAM,QAAqB,MAAM;AAC/C,UAAM,eAAe,kBAAkB,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU;AAAA,MAChE,OAAO,KAAK;AAAA,MACZ,OAAO,EAAE,KAAK,UAAU,KAAK,IAAI;AAAA,IACnC,EAAE;AAEF,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,2BAA2B,QAAQ;AAAA,QAC5C,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,UAAU,OAAO,EAAE,0BAA0B,QAAQ,EAAE;AAAA,UAChE,EAAE,OAAO,QAAQ,OAAO,EAAE,wBAAwB,MAAM,EAAE;AAAA,UAC1D,EAAE,OAAO,YAAY,OAAO,EAAE,4BAA4B,UAAU,EAAE;AAAA,QACxE;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,yBAAyB,MAAM;AAAA,QACxC,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE,GAAG,GAAG,WAAW;AAAA,MAClF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,QACjD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,mCAAmC,aAAa;AAAA,QACzD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,QACjD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,2BAA2B,QAAQ;AAAA,QAC5C,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE,CAAC;AAAA,QAChE,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,0BAA0B,YAAY;AAAA,QAC/C,MAAM;AAAA,QACN,aAAa,EAAE,qCAAqC,sBAAsB;AAAA,MAC5E;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,kBAAkB,MAAM,CAAC,CAAC;AAEjD,QAAM,UAAU,MAAM,QAAsC,MAAM;AAAA,IAChE;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,EAAE,kBAAkB,UAAU;AAAA,MACtC,MAAM;AAAA,QACJ,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MACA,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,OAAO,IAAI;AACjB,cAAM,uBAAuB,2BAA2B,KAAK,IAAI;AACjE,cAAM,oBAAoB,uBACtB,kBAAkB,mBAAmB,oBAAoB,KAAK,OAC9D;AACJ,cAAM,iBAAiB,qBAAqB;AAE5C,eACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,cACP,IAAI,KAAK;AAAA,cACT,MAAM,KAAK;AAAA,cACX,WAAW,oBAAoB,KAAK,IAAI,KAAK,KAAK;AAAA,cAClD,SAAS,KAAK;AAAA,cACd,MAAM,KAAK;AAAA,cACX,YAAY;AAAA,cACZ,UAAW,KAAK,YAAqD;AAAA,cACrE,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,IAAI;AAAA,cAC9C,YAAY,KAAK,cAAc,KAAK,eAAe,KAAK;AAAA,cACxD,YAAY,KAAK;AAAA,cACjB,aAAa,KAAK;AAAA,cAClB,gBAAgB,KAAK;AAAA,cACrB,iBAAiB,KAAK;AAAA,cACtB,YAAY,KAAK;AAAA,cACjB,aAAa,KAAK,eAAe;AAAA,cACjC,QAAQ,KAAK,WAAW;AAAA,YAC1B;AAAA,YACA,SAAS,MAAM,OAAO,KAAK,qBAAqB,KAAK,EAAE,EAAE;AAAA;AAAA,QAC3D;AAAA,MAEJ;AAAA,IACF;AAAA,EACF,GAAG,CAAC,4BAA4B,qBAAqB,mBAAmB,QAAQ,CAAC,CAAC;AAElF,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AAAA,IACxC,EAAE,IAAI,SAAkB,OAAO,EAAE,yBAAyB,OAAO,GAAG,MAAM,MAAM;AAAA,IAChF,EAAE,IAAI,QAAiB,OAAO,EAAE,wBAAwB,MAAM,GAAG,MAAM,KAAK;AAAA,IAC5E,EAAE,IAAI,UAAmB,OAAO,EAAE,0BAA0B,QAAQ,GAAG,MAAM,YAAY;AAAA,IACzF,EAAE,IAAI,YAAqB,OAAO,EAAE,4BAA4B,UAAU,GAAG,MAAM,QAAQ;AAAA,IAC3F,EAAE,IAAI,OAAgB,OAAO,EAAE,uBAAuB,KAAK,GAAG,MAAM,OAAO;AAAA,EAC7E,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,qBAAqB,cAAc,KAAK,CAAC,WAAW,OAAO,OAAO,MAAM,KAAK,cAAc,CAAC;AAClG,QAAM,mBAAmB,mBAAmB;AAE5C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,eAAgB;AACrB,UAAM,qBAAqB,CAAC,UAAsB;AAChD,UAAI,CAAC,cAAc,QAAS;AAC5B,YAAM,SAAS,MAAM;AACrB,UAAI,kBAAkB,QAAQ,CAAC,cAAc,QAAQ,SAAS,MAAM,GAAG;AACrE,0BAAkB,KAAK;AAAA,MACzB;AAAA,IACF;AACA,UAAM,eAAe,CAAC,UAAyB;AAC7C,UAAI,MAAM,QAAQ,SAAU,mBAAkB,KAAK;AAAA,IACrD;AACA,aAAS,iBAAiB,aAAa,kBAAkB;AACzD,aAAS,iBAAiB,WAAW,YAAY;AACjD,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,kBAAkB;AAC5D,eAAS,oBAAoB,WAAW,YAAY;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,OAAO,UAAU,MAAM,SAAS,CAAC;AACvC,QAAM,QAAQ,UAAU,MAAM,SAAS;AACvC,QAAM,aAAa,UAAU,MAAM,cAAc;AAEjD,SACE,qBAAC,SAAI,WAAU,aACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,kBAAkB,UAAU;AAAA,QACrC;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,gBAAgB,CAAC,UAAU;AACzB,oBAAU,KAAK;AACf,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,mBAAmB,EAAE,8BAA8B,iBAAiB;AAAA,QACpE;AAAA,QACA;AAAA,QACA,gBAAgB,CAAC,UAAU;AACzB,0BAAgB,KAAK;AACrB,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,gBAAgB,MAAM;AACpB,0BAAgB,CAAC,CAAC;AAClB,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA;AAAA,QACA,WAAW,UAAU,aAAa,UAAU;AAAA,QAC5C,YAAY;AAAA,UACV;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,cAAc;AAAA,QAChB;AAAA,QACA,SACE,qBAAC,SAAI,WAAU,qCACb;AAAA,+BAAC,SAAI,WAAU,YAAW,KAAK,eAC7B;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,iBAAe;AAAA,gBACf,iBAAc;AAAA,gBACd,SAAS,MAAM,kBAAkB,CAAC,UAAU,CAAC,KAAK;AAAA,gBAElD;AAAA,sCAAC,oBAAiB,WAAU,WAAU,eAAW,MAAC;AAAA,kBAClD,qBAAC,UAAM;AAAA,sBAAE,4BAA4B,QAAQ;AAAA,oBAAE;AAAA,qBAAC;AAAA,kBAChD,oBAAC,UAAM,6BAAmB,OAAM;AAAA,kBAChC,oBAAC,eAAY,WAAU,sBAAqB,eAAW,MAAC;AAAA;AAAA;AAAA,YAC1D;AAAA,YACC,iBACC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,MAAK;AAAA,gBAEJ,wBAAc,IAAI,CAAC,WAAW;AAC7B,wBAAM,OAAO,OAAO;AACpB,wBAAM,WAAW,OAAO,OAAO;AAC/B,yBACE;AAAA,oBAAC;AAAA;AAAA,sBAEC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,MAAK;AAAA,sBACL,gBAAc;AAAA,sBACd,WAAW,+DAA+D,WAAW,iBAAiB,EAAE;AAAA,sBACxG,SAAS,MAAM;AACb,kCAAU,OAAO,EAAE;AACnB,gCAAQ,CAAC;AACT,0CAAkB,KAAK;AAAA,sBACzB;AAAA,sBAEA;AAAA,4CAAC,QAAK,WAAU,WAAU,eAAW,MAAC;AAAA,wBACtC,oBAAC,UAAM,iBAAO,OAAM;AAAA;AAAA;AAAA,oBAdf,OAAO;AAAA,kBAed;AAAA,gBAEJ,CAAC;AAAA;AAAA,YACH,IACE;AAAA,aACN;AAAA,UACA,oBAAC,UAAO,SAAO,MACb,8BAAC,QAAK,MAAK,6BAA6B,YAAE,oBAAoB,iBAAiB,GAAE,GACnF;AAAA,WACF;AAAA,QAEF,YAAY,CAAC,QAAQ;AACnB,iBAAO,KAAK,qBAAqB,IAAI,EAAE,EAAE;AAAA,QAC3C;AAAA,QACA,UAAQ;AAAA;AAAA,IACV;AAAA,IACC;AAAA,KACH;AAEJ;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { getMessageListParticipantLabel } from './messageListLabels'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n recipientCount?: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''\n const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''\n const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''\n const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''\n const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''\n const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''\n const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (since) params.set('since', since)\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(`/api/auth/users?${params.toString()}`)\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n return items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n }, [])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }],\n loadOptions: loadSenderOptions,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),\n },\n ]\n }, [loadSenderOptions, messageTypesQuery.data, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: getMessageListParticipantLabel(item, folder, t),\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n recipientCount: item.recipientCount ?? 0,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
4
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
5
5
|
import { CheckCircle2, FileText, Paperclip, Zap } from "lucide-react";
|
|
@@ -24,15 +24,15 @@ function formatSentTime(value) {
|
|
|
24
24
|
}
|
|
25
25
|
function truncateWords(value, maxWords) {
|
|
26
26
|
const normalized = value.trim().replace(/\s+/g, " ");
|
|
27
|
-
if (!normalized) return "
|
|
27
|
+
if (!normalized) return "";
|
|
28
28
|
const words = normalized.split(" ");
|
|
29
29
|
if (words.length <= maxWords) return normalized;
|
|
30
30
|
return `${words.slice(0, maxWords).join(" ")}...`;
|
|
31
31
|
}
|
|
32
32
|
function DefaultMessageListItem({ message, onClick }) {
|
|
33
33
|
const t = useT();
|
|
34
|
-
const senderLabel = message.senderName || "
|
|
35
|
-
const subject = message.subject || "
|
|
34
|
+
const senderLabel = message.senderName?.trim() || t("messages.list.noRecipient", "(No recipient)");
|
|
35
|
+
const subject = message.subject.trim() || t("messages.list.noSubject", "(No subject)");
|
|
36
36
|
const absoluteSentAt = formatDateTime(message.sentAt);
|
|
37
37
|
const sentAtLabel = formatSentTime(message.sentAt);
|
|
38
38
|
const bodyPreview = truncateWords(message.body || "", 16);
|
|
@@ -61,8 +61,10 @@ function DefaultMessageListItem({ message, onClick }) {
|
|
|
61
61
|
/* @__PURE__ */ jsx("span", { className: cn("w-40 flex-shrink-0 truncate text-sm md:w-56", message.unread ? "font-semibold" : "font-normal"), children: senderLabel }),
|
|
62
62
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [
|
|
63
63
|
/* @__PURE__ */ jsx("span", { className: cn("truncate text-foreground", message.unread ? "font-semibold" : "font-normal"), children: subject }),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
|
|
64
|
+
bodyPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
|
|
66
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: bodyPreview })
|
|
67
|
+
] }) : null
|
|
66
68
|
] }),
|
|
67
69
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-1.5 text-muted-foreground", children: [
|
|
68
70
|
message.hasObjects ? /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": true }) : null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/messages/components/defaults/DefaultMessageListItem.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { CheckCircle2, FileText, Paperclip, Zap } from 'lucide-react'\n\nfunction formatDateTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatSentTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n\n const now = new Date()\n const isSameDay = now.toDateString() === value.toDateString()\n if (isSameDay) {\n return value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n }\n\n const isSameYear = now.getFullYear() === value.getFullYear()\n if (isSameYear) {\n return value.toLocaleDateString([], { month: 'short', day: 'numeric' })\n }\n\n return value.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction truncateWords(value: string, maxWords: number): string {\n const normalized = value.trim().replace(/\\s+/g, ' ')\n if (!normalized) return '
|
|
5
|
-
"mappings": ";AAqEQ,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { CheckCircle2, FileText, Paperclip, Zap } from 'lucide-react'\n\nfunction formatDateTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatSentTime(value: Date | null): string {\n if (!value) return '\u2014'\n if (Number.isNaN(value.getTime())) return '\u2014'\n\n const now = new Date()\n const isSameDay = now.toDateString() === value.toDateString()\n if (isSameDay) {\n return value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n }\n\n const isSameYear = now.getFullYear() === value.getFullYear()\n if (isSameYear) {\n return value.toLocaleDateString([], { month: 'short', day: 'numeric' })\n }\n\n return value.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction truncateWords(value: string, maxWords: number): string {\n const normalized = value.trim().replace(/\\s+/g, ' ')\n if (!normalized) return ''\n const words = normalized.split(' ')\n if (words.length <= maxWords) return normalized\n return `${words.slice(0, maxWords).join(' ')}...`\n}\n\nexport function DefaultMessageListItem({ message, onClick }: MessageListItemProps) {\n const t = useT()\n const senderLabel = message.senderName?.trim() || t('messages.list.noRecipient', '(No recipient)')\n const subject = message.subject.trim() || t('messages.list.noSubject', '(No subject)')\n const absoluteSentAt = formatDateTime(message.sentAt)\n const sentAtLabel = formatSentTime(message.sentAt)\n const bodyPreview = truncateWords(message.body || '', 16)\n\n return (\n <div\n className={cn(\n 'group min-w-0 w-full cursor-pointer rounded-md px-2 py-2 transition-colors hover:bg-muted/50',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n role=\"button\"\n tabIndex={0}\n aria-label={t('messages.openMessageDetailedA11y', 'Open message {subject} from {sender}, sent {sentAt}', {\n subject,\n sender: senderLabel,\n sentAt: absoluteSentAt,\n })}\n onClick={onClick}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onClick()\n }\n }}\n >\n <div className=\"flex items-center gap-3\">\n <span className={cn('w-40 flex-shrink-0 truncate text-sm md:w-56', message.unread ? 'font-semibold' : 'font-normal')}>\n {senderLabel}\n </span>\n\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5 text-sm\">\n <span className={cn('truncate text-foreground', message.unread ? 'font-semibold' : 'font-normal')}>\n {subject}\n </span>\n {bodyPreview ? (\n <>\n <span className=\"text-muted-foreground\">-</span>\n <span className=\"truncate text-muted-foreground\">{bodyPreview}</span>\n </>\n ) : null}\n </div>\n\n <div className=\"flex flex-shrink-0 items-center gap-1.5 text-muted-foreground\">\n {message.hasObjects ? <FileText className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasAttachments ? <Paperclip className=\"h-3.5 w-3.5\" aria-hidden /> : null}\n {message.hasActions && !message.actionTaken ? (\n <Zap className=\"h-3.5 w-3.5 text-amber-600\" aria-hidden />\n ) : null}\n {message.actionTaken ? <CheckCircle2 className=\"h-3.5 w-3.5 text-green-600\" aria-hidden /> : null}\n </div>\n\n <span\n className={cn('flex-shrink-0 text-xs', message.unread ? 'font-semibold text-foreground' : 'font-normal text-muted-foreground')}\n title={absoluteSentAt}\n >\n {sentAtLabel}\n </span>\n </div>\n </div>\n )\n}\n\nexport default DefaultMessageListItem\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function normalizeLabel(value) {
|
|
2
|
+
const normalized = value?.trim();
|
|
3
|
+
return normalized ? normalized : null;
|
|
4
|
+
}
|
|
5
|
+
function getMessageListParticipantLabel(item, folder, t) {
|
|
6
|
+
if ((folder === "sent" || folder === "drafts") && Number(item.recipientCount ?? 0) <= 0) {
|
|
7
|
+
return t("messages.list.noRecipient", "(No recipient)");
|
|
8
|
+
}
|
|
9
|
+
return normalizeLabel(item.senderName) ?? normalizeLabel(item.senderEmail) ?? item.senderUserId;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
getMessageListParticipantLabel
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=messageListLabels.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/messages/components/messageListLabels.ts"],
|
|
4
|
+
"sourcesContent": ["import type { MessageFolder } from './useMessagesInboxBulkActions'\n\ntype Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string\n\ntype MessageParticipantSource = {\n senderName?: string | null\n senderEmail?: string | null\n senderUserId: string\n recipientCount?: number | null\n}\n\nfunction normalizeLabel(value: string | null | undefined): string | null {\n const normalized = value?.trim()\n return normalized ? normalized : null\n}\n\nexport function getMessageListParticipantLabel(\n item: MessageParticipantSource,\n folder: MessageFolder,\n t: Translate,\n): string {\n if ((folder === 'sent' || folder === 'drafts') && Number(item.recipientCount ?? 0) <= 0) {\n return t('messages.list.noRecipient', '(No recipient)')\n }\n\n return normalizeLabel(item.senderName)\n ?? normalizeLabel(item.senderEmail)\n ?? item.senderUserId\n}\n"],
|
|
5
|
+
"mappings": "AAWA,SAAS,eAAe,OAAiD;AACvE,QAAM,aAAa,OAAO,KAAK;AAC/B,SAAO,aAAa,aAAa;AACnC;AAEO,SAAS,+BACd,MACA,QACA,GACQ;AACR,OAAK,WAAW,UAAU,WAAW,aAAa,OAAO,KAAK,kBAAkB,CAAC,KAAK,GAAG;AACvF,WAAO,EAAE,6BAA6B,gBAAgB;AAAA,EACxD;AAEA,SAAO,eAAe,KAAK,UAAU,KAChC,eAAe,KAAK,WAAW,KAC/B,KAAK;AACZ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -41,6 +41,39 @@ const MESSAGE_BULK_REQUESTS = {
|
|
|
41
41
|
errorFallback: "Failed to delete message."
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
const MESSAGE_BULK_ACTIONS = {
|
|
45
|
+
markRead: {
|
|
46
|
+
id: "messages-mark-read",
|
|
47
|
+
labelKey: "messages.actions.markRead",
|
|
48
|
+
labelFallback: "Mark read"
|
|
49
|
+
},
|
|
50
|
+
markUnread: {
|
|
51
|
+
id: "messages-mark-unread",
|
|
52
|
+
labelKey: "messages.actions.markUnread",
|
|
53
|
+
labelFallback: "Mark unread"
|
|
54
|
+
},
|
|
55
|
+
archive: {
|
|
56
|
+
id: "messages-archive",
|
|
57
|
+
labelKey: "messages.actions.archive",
|
|
58
|
+
labelFallback: "Archive"
|
|
59
|
+
},
|
|
60
|
+
delete: {
|
|
61
|
+
id: "messages-delete",
|
|
62
|
+
labelKey: "messages.actions.delete",
|
|
63
|
+
labelFallback: "Delete",
|
|
64
|
+
destructive: true
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const MESSAGE_BULK_ACTION_IDS_BY_FOLDER = {
|
|
68
|
+
inbox: ["markRead", "markUnread", "archive", "delete"],
|
|
69
|
+
archived: ["markRead", "markUnread", "delete"],
|
|
70
|
+
sent: ["delete"],
|
|
71
|
+
drafts: ["delete"],
|
|
72
|
+
all: []
|
|
73
|
+
};
|
|
74
|
+
function getMessageBulkActionIdsForFolder(folder) {
|
|
75
|
+
return [...MESSAGE_BULK_ACTION_IDS_BY_FOLDER[folder]];
|
|
76
|
+
}
|
|
44
77
|
function normalizeSelectionScopeValue(value) {
|
|
45
78
|
if (value == null) return void 0;
|
|
46
79
|
if (typeof value === "string") {
|
|
@@ -197,29 +230,19 @@ function useMessagesInboxBulkActions({
|
|
|
197
230
|
}
|
|
198
231
|
}, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t]);
|
|
199
232
|
const bulkActions = React.useMemo(
|
|
200
|
-
() =>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
label: t("messages.actions.archive", "Archive"),
|
|
214
|
-
onExecute: (selectedRows) => executeBulkAction("archive", selectedRows)
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
id: "messages-delete",
|
|
218
|
-
label: t("messages.actions.delete", "Delete"),
|
|
219
|
-
destructive: true,
|
|
220
|
-
onExecute: (selectedRows) => executeBulkAction("delete", selectedRows)
|
|
221
|
-
}
|
|
222
|
-
] : void 0,
|
|
233
|
+
() => {
|
|
234
|
+
const actionIds = getMessageBulkActionIdsForFolder(folder);
|
|
235
|
+
if (actionIds.length === 0) return void 0;
|
|
236
|
+
return actionIds.map((actionId) => {
|
|
237
|
+
const action = MESSAGE_BULK_ACTIONS[actionId];
|
|
238
|
+
return {
|
|
239
|
+
id: action.id,
|
|
240
|
+
label: t(action.labelKey, action.labelFallback),
|
|
241
|
+
destructive: action.destructive,
|
|
242
|
+
onExecute: (selectedRows) => executeBulkAction(actionId, selectedRows)
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
},
|
|
223
246
|
[executeBulkAction, folder, t]
|
|
224
247
|
);
|
|
225
248
|
return {
|
|
@@ -230,6 +253,7 @@ function useMessagesInboxBulkActions({
|
|
|
230
253
|
};
|
|
231
254
|
}
|
|
232
255
|
export {
|
|
256
|
+
getMessageBulkActionIdsForFolder,
|
|
233
257
|
useMessagesInboxBulkActions
|
|
234
258
|
};
|
|
235
259
|
//# sourceMappingURL=useMessagesInboxBulkActions.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/useMessagesInboxBulkActions.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { BulkAction } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { toErrorMessage } from './message-detail/utils'\n\nexport type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'\n\ntype MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'\n\ntype BulkExecutionSummary = {\n action: MessageBulkActionId\n total: number\n succeeded: number\n failed: number\n}\n\ntype MessageBulkRequestConfig = {\n method: 'PUT' | 'DELETE'\n buildUrl: (messageId: string) => string\n successKey: string\n successFallback: string\n errorKey: string\n errorFallback: string\n}\n\ntype MessageInboxBulkMutationContext = {\n actionId: MessageBulkActionId\n messageIds: string[]\n folder: MessageFolder\n page: number\n search: string\n filters: FilterValues\n retryLastMutation: () => Promise<boolean>\n}\n\ntype UseMessagesInboxBulkActionsInput = {\n folder: MessageFolder\n page: number\n search: string\n filterValues: FilterValues\n}\n\ntype MessageInboxBulkRow = {\n id: string\n}\n\nconst MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfig> = {\n markRead: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markReadSuccess',\n successFallback: '{count} messages marked as read.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n markUnread: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markUnreadSuccess',\n successFallback: '{count} messages marked as unread.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n archive: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/archive`,\n successKey: 'messages.bulk.flash.archiveSuccess',\n successFallback: '{count} messages archived.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n delete: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}`,\n successKey: 'messages.bulk.flash.deleteSuccess',\n successFallback: '{count} messages deleted.',\n errorKey: 'messages.errors.deleteFailed',\n errorFallback: 'Failed to delete message.',\n },\n}\n\nfunction normalizeSelectionScopeValue(value: unknown): unknown {\n if (value == null) return undefined\n if (typeof value === 'string') {\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n }\n if (Array.isArray(value)) {\n const normalized = value\n .map((item) => normalizeSelectionScopeValue(item))\n .filter((item) => item !== undefined)\n return normalized.length > 0 ? normalized : undefined\n }\n if (typeof value === 'object') {\n const normalizedEntries = Object.entries(value)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, nestedValue]) => [key, normalizeSelectionScopeValue(nestedValue)] as const)\n .filter(([, nestedValue]) => nestedValue !== undefined)\n if (normalizedEntries.length === 0) return undefined\n return Object.fromEntries(normalizedEntries)\n }\n return value\n}\n\nfunction buildMessageSelectionScopeKey(\n folder: MessageFolder,\n page: number,\n search: string,\n filterValues: FilterValues,\n): string {\n return JSON.stringify({\n folder,\n page,\n search: search.trim(),\n filters: normalizeSelectionScopeValue(filterValues) ?? {},\n })\n}\n\nasync function runWithConcurrency<TItem>(\n items: TItem[],\n limit: number,\n worker: (item: TItem) => Promise<void>,\n): Promise<PromiseSettledResult<void>[]> {\n if (items.length === 0) return []\n\n const results: PromiseSettledResult<void>[] = new Array(items.length)\n let nextIndex = 0\n\n const runWorker = async () => {\n while (nextIndex < items.length) {\n const currentIndex = nextIndex\n nextIndex += 1\n try {\n await worker(items[currentIndex])\n results[currentIndex] = { status: 'fulfilled', value: undefined }\n } catch (error) {\n results[currentIndex] = { status: 'rejected', reason: error }\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, () => runWorker()),\n )\n\n return results\n}\n\nexport function useMessagesInboxBulkActions<T extends MessageInboxBulkRow>({\n folder,\n page,\n search,\n filterValues,\n}: UseMessagesInboxBulkActionsInput): {\n bulkActions: BulkAction<T>[] | undefined\n selectionScopeKey: string\n injectionContext: Record<string, unknown>\n ConfirmDialogElement: React.ReactNode\n} {\n const t = useT()\n const queryClient = useQueryClient()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<MessageInboxBulkMutationContext>({\n contextId: 'messages-inbox-bulk-actions',\n })\n\n const selectionScopeKey = React.useMemo(\n () => buildMessageSelectionScopeKey(folder, page, search, filterValues),\n [filterValues, folder, page, search],\n )\n const injectionContext = React.useMemo<Record<string, unknown>>(\n () => ({\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n }),\n [filterValues, folder, page, retryLastMutation, search],\n )\n\n const executeBulkAction = React.useCallback(async (\n actionId: MessageBulkActionId,\n selectedRows: T[],\n ): Promise<boolean> => {\n const messageIds = selectedRows.map((row) => row.id).filter((id) => id.trim().length > 0)\n if (messageIds.length === 0) return false\n\n if (actionId === 'delete') {\n const confirmed = await confirm({\n title: t('messages.bulk.delete.title', 'Delete {count} messages?', { count: messageIds.length }),\n description: t('messages.bulk.delete.description', 'This removes the selected messages from your view.'),\n confirmText: t('messages.actions.delete', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return false\n }\n\n const requestConfig = MESSAGE_BULK_REQUESTS[actionId]\n\n try {\n const summary = await runMutation<BulkExecutionSummary>({\n operation: async () => {\n const results = await runWithConcurrency(messageIds, 5, async (messageId) => {\n const call = await apiCall<{ ok?: boolean }>(requestConfig.buildUrl(messageId), {\n method: requestConfig.method,\n })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t(requestConfig.errorKey, requestConfig.errorFallback),\n )\n }\n })\n\n const failed = results.filter((result) => result.status === 'rejected').length\n const succeeded = results.length - failed\n\n if (succeeded > 0) {\n await queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }\n\n return {\n action: actionId,\n total: messageIds.length,\n succeeded,\n failed,\n }\n },\n context: {\n actionId,\n messageIds,\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n },\n mutationPayload: {\n actionId,\n messageIds,\n },\n })\n\n if (summary.succeeded === 0) {\n flash(\n t('messages.bulk.flash.failed', 'Failed to process {count} messages.', { count: summary.failed }),\n 'error',\n )\n return false\n }\n\n if (summary.failed > 0) {\n flash(\n t('messages.bulk.flash.partial', '{succeeded} of {total} messages processed; {failed} failed.', {\n succeeded: summary.succeeded,\n total: summary.total,\n failed: summary.failed,\n }),\n 'warning',\n )\n return true\n }\n\n flash(\n t(requestConfig.successKey, requestConfig.successFallback, { count: summary.succeeded }),\n 'success',\n )\n return true\n } catch (error) {\n flash(\n error instanceof Error\n ? error.message\n : t(requestConfig.errorKey, requestConfig.errorFallback),\n 'error',\n )\n return false\n }\n }, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t])\n\n const bulkActions = React.useMemo<BulkAction<T>[] | undefined>(\n () => folder === 'inbox'\n ? [\n {\n id: 'messages-mark-read',\n label: t('messages.actions.markRead', 'Mark read'),\n onExecute: (selectedRows: T[]) => executeBulkAction('markRead', selectedRows),\n },\n {\n id: 'messages-mark-unread',\n label: t('messages.actions.markUnread', 'Mark unread'),\n onExecute: (selectedRows: T[]) => executeBulkAction('markUnread', selectedRows),\n },\n {\n id: 'messages-archive',\n label: t('messages.actions.archive', 'Archive'),\n onExecute: (selectedRows: T[]) => executeBulkAction('archive', selectedRows),\n },\n {\n id: 'messages-delete',\n label: t('messages.actions.delete', 'Delete'),\n destructive: true,\n onExecute: (selectedRows: T[]) => executeBulkAction('delete', selectedRows),\n },\n ]\n : undefined,\n [executeBulkAction, folder, t],\n )\n\n return {\n bulkActions,\n selectionScopeKey,\n injectionContext,\n ConfirmDialogElement,\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAEA,YAAY,WAAW;AACvB,SAAS,sBAAsB;AAC/B,SAAS,YAAY;AAGrB,SAAS,wBAAwB;AACjC,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,sBAAsB;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { BulkAction } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { toErrorMessage } from './message-detail/utils'\n\nexport type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'\n\nexport type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'\n\ntype BulkExecutionSummary = {\n action: MessageBulkActionId\n total: number\n succeeded: number\n failed: number\n}\n\ntype MessageBulkRequestConfig = {\n method: 'PUT' | 'DELETE'\n buildUrl: (messageId: string) => string\n successKey: string\n successFallback: string\n errorKey: string\n errorFallback: string\n}\n\ntype MessageBulkActionDefinition = {\n id: string\n labelKey: string\n labelFallback: string\n destructive?: boolean\n}\n\ntype MessageInboxBulkMutationContext = {\n actionId: MessageBulkActionId\n messageIds: string[]\n folder: MessageFolder\n page: number\n search: string\n filters: FilterValues\n retryLastMutation: () => Promise<boolean>\n}\n\ntype UseMessagesInboxBulkActionsInput = {\n folder: MessageFolder\n page: number\n search: string\n filterValues: FilterValues\n}\n\ntype MessageInboxBulkRow = {\n id: string\n}\n\nconst MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfig> = {\n markRead: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markReadSuccess',\n successFallback: '{count} messages marked as read.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n markUnread: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/read`,\n successKey: 'messages.bulk.flash.markUnreadSuccess',\n successFallback: '{count} messages marked as unread.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n archive: {\n method: 'PUT',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}/archive`,\n successKey: 'messages.bulk.flash.archiveSuccess',\n successFallback: '{count} messages archived.',\n errorKey: 'messages.errors.stateChangeFailed',\n errorFallback: 'Failed to update message state.',\n },\n delete: {\n method: 'DELETE',\n buildUrl: (messageId) => `/api/messages/${encodeURIComponent(messageId)}`,\n successKey: 'messages.bulk.flash.deleteSuccess',\n successFallback: '{count} messages deleted.',\n errorKey: 'messages.errors.deleteFailed',\n errorFallback: 'Failed to delete message.',\n },\n}\n\nconst MESSAGE_BULK_ACTIONS: Record<MessageBulkActionId, MessageBulkActionDefinition> = {\n markRead: {\n id: 'messages-mark-read',\n labelKey: 'messages.actions.markRead',\n labelFallback: 'Mark read',\n },\n markUnread: {\n id: 'messages-mark-unread',\n labelKey: 'messages.actions.markUnread',\n labelFallback: 'Mark unread',\n },\n archive: {\n id: 'messages-archive',\n labelKey: 'messages.actions.archive',\n labelFallback: 'Archive',\n },\n delete: {\n id: 'messages-delete',\n labelKey: 'messages.actions.delete',\n labelFallback: 'Delete',\n destructive: true,\n },\n}\n\nconst MESSAGE_BULK_ACTION_IDS_BY_FOLDER = {\n inbox: ['markRead', 'markUnread', 'archive', 'delete'],\n archived: ['markRead', 'markUnread', 'delete'],\n sent: ['delete'],\n drafts: ['delete'],\n all: [],\n} satisfies Record<MessageFolder, readonly MessageBulkActionId[]>\n\nexport function getMessageBulkActionIdsForFolder(folder: MessageFolder): MessageBulkActionId[] {\n return [...MESSAGE_BULK_ACTION_IDS_BY_FOLDER[folder]]\n}\n\nfunction normalizeSelectionScopeValue(value: unknown): unknown {\n if (value == null) return undefined\n if (typeof value === 'string') {\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n }\n if (Array.isArray(value)) {\n const normalized = value\n .map((item) => normalizeSelectionScopeValue(item))\n .filter((item) => item !== undefined)\n return normalized.length > 0 ? normalized : undefined\n }\n if (typeof value === 'object') {\n const normalizedEntries = Object.entries(value)\n .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))\n .map(([key, nestedValue]) => [key, normalizeSelectionScopeValue(nestedValue)] as const)\n .filter(([, nestedValue]) => nestedValue !== undefined)\n if (normalizedEntries.length === 0) return undefined\n return Object.fromEntries(normalizedEntries)\n }\n return value\n}\n\nfunction buildMessageSelectionScopeKey(\n folder: MessageFolder,\n page: number,\n search: string,\n filterValues: FilterValues,\n): string {\n return JSON.stringify({\n folder,\n page,\n search: search.trim(),\n filters: normalizeSelectionScopeValue(filterValues) ?? {},\n })\n}\n\nasync function runWithConcurrency<TItem>(\n items: TItem[],\n limit: number,\n worker: (item: TItem) => Promise<void>,\n): Promise<PromiseSettledResult<void>[]> {\n if (items.length === 0) return []\n\n const results: PromiseSettledResult<void>[] = new Array(items.length)\n let nextIndex = 0\n\n const runWorker = async () => {\n while (nextIndex < items.length) {\n const currentIndex = nextIndex\n nextIndex += 1\n try {\n await worker(items[currentIndex])\n results[currentIndex] = { status: 'fulfilled', value: undefined }\n } catch (error) {\n results[currentIndex] = { status: 'rejected', reason: error }\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, () => runWorker()),\n )\n\n return results\n}\n\nexport function useMessagesInboxBulkActions<T extends MessageInboxBulkRow>({\n folder,\n page,\n search,\n filterValues,\n}: UseMessagesInboxBulkActionsInput): {\n bulkActions: BulkAction<T>[] | undefined\n selectionScopeKey: string\n injectionContext: Record<string, unknown>\n ConfirmDialogElement: React.ReactNode\n} {\n const t = useT()\n const queryClient = useQueryClient()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<MessageInboxBulkMutationContext>({\n contextId: 'messages-inbox-bulk-actions',\n })\n\n const selectionScopeKey = React.useMemo(\n () => buildMessageSelectionScopeKey(folder, page, search, filterValues),\n [filterValues, folder, page, search],\n )\n const injectionContext = React.useMemo<Record<string, unknown>>(\n () => ({\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n }),\n [filterValues, folder, page, retryLastMutation, search],\n )\n\n const executeBulkAction = React.useCallback(async (\n actionId: MessageBulkActionId,\n selectedRows: T[],\n ): Promise<boolean> => {\n const messageIds = selectedRows.map((row) => row.id).filter((id) => id.trim().length > 0)\n if (messageIds.length === 0) return false\n\n if (actionId === 'delete') {\n const confirmed = await confirm({\n title: t('messages.bulk.delete.title', 'Delete {count} messages?', { count: messageIds.length }),\n description: t('messages.bulk.delete.description', 'This removes the selected messages from your view.'),\n confirmText: t('messages.actions.delete', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return false\n }\n\n const requestConfig = MESSAGE_BULK_REQUESTS[actionId]\n\n try {\n const summary = await runMutation<BulkExecutionSummary>({\n operation: async () => {\n const results = await runWithConcurrency(messageIds, 5, async (messageId) => {\n const call = await apiCall<{ ok?: boolean }>(requestConfig.buildUrl(messageId), {\n method: requestConfig.method,\n })\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t(requestConfig.errorKey, requestConfig.errorFallback),\n )\n }\n })\n\n const failed = results.filter((result) => result.status === 'rejected').length\n const succeeded = results.length - failed\n\n if (succeeded > 0) {\n await queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }\n\n return {\n action: actionId,\n total: messageIds.length,\n succeeded,\n failed,\n }\n },\n context: {\n actionId,\n messageIds,\n folder,\n page,\n search: search.trim(),\n filters: filterValues,\n retryLastMutation,\n },\n mutationPayload: {\n actionId,\n messageIds,\n },\n })\n\n if (summary.succeeded === 0) {\n flash(\n t('messages.bulk.flash.failed', 'Failed to process {count} messages.', { count: summary.failed }),\n 'error',\n )\n return false\n }\n\n if (summary.failed > 0) {\n flash(\n t('messages.bulk.flash.partial', '{succeeded} of {total} messages processed; {failed} failed.', {\n succeeded: summary.succeeded,\n total: summary.total,\n failed: summary.failed,\n }),\n 'warning',\n )\n return true\n }\n\n flash(\n t(requestConfig.successKey, requestConfig.successFallback, { count: summary.succeeded }),\n 'success',\n )\n return true\n } catch (error) {\n flash(\n error instanceof Error\n ? error.message\n : t(requestConfig.errorKey, requestConfig.errorFallback),\n 'error',\n )\n return false\n }\n }, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t])\n\n const bulkActions = React.useMemo<BulkAction<T>[] | undefined>(\n () => {\n const actionIds = getMessageBulkActionIdsForFolder(folder)\n if (actionIds.length === 0) return undefined\n\n return actionIds.map((actionId) => {\n const action = MESSAGE_BULK_ACTIONS[actionId]\n return {\n id: action.id,\n label: t(action.labelKey, action.labelFallback),\n destructive: action.destructive,\n onExecute: (selectedRows: T[]) => executeBulkAction(actionId, selectedRows),\n }\n })\n },\n [executeBulkAction, folder, t],\n )\n\n return {\n bulkActions,\n selectionScopeKey,\n injectionContext,\n ConfirmDialogElement,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.2-develop.
|
|
3
|
+
"version": "0.6.2-develop.3467.1.2a1818709d",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -242,16 +242,16 @@
|
|
|
242
242
|
"zod": "^4.3.6"
|
|
243
243
|
},
|
|
244
244
|
"peerDependencies": {
|
|
245
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
246
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
247
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
245
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3467.1.2a1818709d",
|
|
246
|
+
"@open-mercato/shared": "0.6.2-develop.3467.1.2a1818709d",
|
|
247
|
+
"@open-mercato/ui": "0.6.2-develop.3467.1.2a1818709d",
|
|
248
248
|
"react": "^19.0.0",
|
|
249
249
|
"react-dom": "^19.0.0"
|
|
250
250
|
},
|
|
251
251
|
"devDependencies": {
|
|
252
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
253
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
254
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
252
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3467.1.2a1818709d",
|
|
253
|
+
"@open-mercato/shared": "0.6.2-develop.3467.1.2a1818709d",
|
|
254
|
+
"@open-mercato/ui": "0.6.2-develop.3467.1.2a1818709d",
|
|
255
255
|
"@testing-library/dom": "^10.4.1",
|
|
256
256
|
"@testing-library/jest-dom": "^6.9.1",
|
|
257
257
|
"@testing-library/react": "^16.3.1",
|
|
@@ -20,6 +20,7 @@ import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern
|
|
|
20
20
|
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
21
21
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
22
22
|
import { sql } from 'kysely'
|
|
23
|
+
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
23
24
|
|
|
24
25
|
const querySchema = z.object({
|
|
25
26
|
id: z.string().uuid().optional(),
|
|
@@ -36,12 +37,8 @@ const rawBodySchema = z.object({}).passthrough()
|
|
|
36
37
|
const passwordSchema = buildPasswordSchema()
|
|
37
38
|
|
|
38
39
|
const displayNameSchema = z.preprocess(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const trimmed = value.trim()
|
|
42
|
-
return trimmed.length ? trimmed : undefined
|
|
43
|
-
},
|
|
44
|
-
z.string().trim().min(1).max(120).optional(),
|
|
40
|
+
normalizeDisplayNameInput,
|
|
41
|
+
z.string().trim().min(1).max(120).nullable().optional(),
|
|
45
42
|
)
|
|
46
43
|
|
|
47
44
|
const userCreateSchema = z.object({
|