@open-mercato/core 0.6.4-develop.4254.1.7a123d970c → 0.6.4-develop.4264.1.53368d85fe
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/dist/modules/customer_accounts/api/portal/events/stream.js +1 -0
- package/dist/modules/customer_accounts/api/portal/events/stream.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/api/portal/events/stream.ts +6 -0
- package/src/modules/staff/i18n/de.json +23 -0
- package/src/modules/staff/i18n/en.json +23 -0
- package/src/modules/staff/i18n/es.json +23 -0
- package/src/modules/staff/i18n/pl.json +23 -0
|
@@ -124,6 +124,7 @@ async function GET(req) {
|
|
|
124
124
|
close: () => controller.close()
|
|
125
125
|
};
|
|
126
126
|
portalConnections.add(connection);
|
|
127
|
+
controller.enqueue(encoder.encode(": connected\n\n"));
|
|
127
128
|
heartbeatTimer = setInterval(() => {
|
|
128
129
|
try {
|
|
129
130
|
controller.enqueue(encoder.encode(":heartbeat\n\n"));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customer_accounts/api/portal/events/stream.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Portal SSE Event Stream \u2014 Portal Event Bridge\n *\n * Server-Sent Events endpoint that bridges server-side events to the customer portal.\n * Only events with `portalBroadcast: true` in their EventDefinition are sent.\n * Events are scoped to the authenticated customer's tenant and organization.\n *\n * Uses customer JWT auth (cookie or Bearer token) instead of staff auth.\n *\n * Client consumer: `packages/ui/src/portal/hooks/usePortalEventBridge.ts`\n */\n\nimport { NextResponse } from 'next/server'\nimport { isPortalBroadcastEvent } from '@open-mercato/shared/modules/events'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst MAX_PAYLOAD_BYTES = 4096\n\ntype PortalSseConnection = {\n tenantId: string\n organizationId: string\n customerUserId: string\n send: (data: string) => void\n close: () => void\n}\n\nfunction normalizeAudience(data: Record<string, unknown>): {\n tenantId: string | null\n organizationScopes: string[]\n recipientUserScopes: string[]\n} {\n const tenantId = typeof data.tenantId === 'string' ? data.tenantId : null\n const organizationScopes = new Set<string>()\n if (typeof data.organizationId === 'string' && data.organizationId.trim().length > 0) {\n organizationScopes.add(data.organizationId.trim())\n }\n if (Array.isArray(data.organizationIds)) {\n for (const orgId of data.organizationIds) {\n if (typeof orgId === 'string' && orgId.trim().length > 0) {\n organizationScopes.add(orgId.trim())\n }\n }\n }\n\n const recipientUserScopes = new Set<string>()\n if (typeof data.recipientUserId === 'string' && data.recipientUserId.trim().length > 0) {\n recipientUserScopes.add(data.recipientUserId.trim())\n }\n if (Array.isArray(data.recipientUserIds)) {\n for (const userId of data.recipientUserIds) {\n if (typeof userId === 'string' && userId.trim().length > 0) {\n recipientUserScopes.add(userId.trim())\n }\n }\n }\n\n return {\n tenantId,\n organizationScopes: Array.from(organizationScopes),\n recipientUserScopes: Array.from(recipientUserScopes),\n }\n}\n\nfunction matchesAudience(conn: PortalSseConnection, audience: ReturnType<typeof normalizeAudience>): boolean {\n if (!audience.tenantId) return false\n if (conn.tenantId !== audience.tenantId) return false\n if (audience.organizationScopes.length > 0) {\n if (!audience.organizationScopes.includes(conn.organizationId)) return false\n }\n if (audience.recipientUserScopes.length > 0 && !audience.recipientUserScopes.includes(conn.customerUserId)) {\n return false\n }\n return true\n}\n\nconst portalConnections = new Set<PortalSseConnection>()\n\nlet portalTapRegistered = false\n\nasync function broadcastPortalEvent(eventName: string, payload: Record<string, unknown>): Promise<void> {\n if (!eventName || portalConnections.size === 0) return\n if (!isPortalBroadcastEvent(eventName)) return\n\n const data = payload ?? {}\n const audience = normalizeAudience(data)\n const organizationId = audience.organizationScopes[0] ?? ''\n\n let ssePayload = JSON.stringify({\n id: eventName,\n payload: data,\n timestamp: Date.now(),\n organizationId,\n })\n\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n const entityRef: Record<string, unknown> = { truncated: true }\n if (typeof data.id === 'string' && data.id.trim().length > 0) entityRef.id = data.id.trim()\n if (typeof data.entityId === 'string' && data.entityId.trim().length > 0) entityRef.entityId = data.entityId.trim()\n ssePayload = JSON.stringify({\n id: eventName,\n payload: entityRef,\n timestamp: Date.now(),\n organizationId,\n })\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n return\n }\n }\n\n for (const conn of portalConnections) {\n if (!matchesAudience(conn, audience)) continue\n try {\n conn.send(ssePayload)\n } catch {\n // Connection may have been closed\n }\n }\n}\n\nfunction ensurePortalTap(): void {\n if (portalTapRegistered) return\n portalTapRegistered = true\n\n // Dynamically import to avoid circular dependency \u2014 the events bus\n // registers a global tap that fires for every emitted event.\n import('@open-mercato/events/bus').then(({ registerGlobalEventTap, registerCrossProcessEventListener }) => {\n registerGlobalEventTap(async (eventName, payload) => {\n await broadcastPortalEvent(eventName, (payload ?? {}) as Record<string, unknown>)\n })\n\n registerCrossProcessEventListener(async (envelope) => {\n if (envelope.originPid === process.pid) return\n await broadcastPortalEvent(\n envelope.event,\n (envelope.payload ?? {}) as Record<string, unknown>,\n )\n })\n }).catch(() => {\n // Silently ignore if events package is not available\n portalTapRegistered = false\n })\n}\n\nexport async function GET(req: Request): Promise<Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n ensurePortalTap()\n\n const encoder = new TextEncoder()\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null\n let connection: PortalSseConnection | null = null\n const onAbort = () => cleanup()\n\n const stream = new ReadableStream({\n start(controller) {\n const send = (data: string) => {\n controller.enqueue(encoder.encode(`data: ${data}\\n\\n`))\n }\n\n connection = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n customerUserId: auth.sub,\n send,\n close: () => controller.close(),\n }\n portalConnections.add(connection)\n\n heartbeatTimer = setInterval(() => {\n try {\n controller.enqueue(encoder.encode(':heartbeat\\n\\n'))\n } catch {\n // Stream may have been closed\n }\n }, HEARTBEAT_INTERVAL_MS)\n },\n cancel() {\n cleanup()\n },\n })\n\n function cleanup() {\n if (heartbeatTimer) {\n clearInterval(heartbeatTimer)\n heartbeatTimer = null\n }\n if (connection) {\n portalConnections.delete(connection)\n connection = null\n }\n // Detach from the request signal so reconnect churn does not accumulate\n // listeners and closures on long-lived AbortSignals.\n req.signal.removeEventListener('abort', onAbort)\n }\n\n req.signal.addEventListener('abort', onAbort, { once: true })\n\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n })\n}\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Subscribe to portal events via SSE (Portal Event Bridge)',\n description: 'Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer\\'s tenant, organization, and recipient user audience.',\n tags: ['Customer Portal'],\n responses: [\n {\n status: 200,\n description: 'Event stream (text/event-stream)',\n },\n ],\n errors: [\n { status: 401, description: 'Not authenticated' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Portal event stream',\n methods: { GET: methodDoc },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Portal SSE Event Stream \u2014 Portal Event Bridge\n *\n * Server-Sent Events endpoint that bridges server-side events to the customer portal.\n * Only events with `portalBroadcast: true` in their EventDefinition are sent.\n * Events are scoped to the authenticated customer's tenant and organization.\n *\n * Uses customer JWT auth (cookie or Bearer token) instead of staff auth.\n *\n * Client consumer: `packages/ui/src/portal/hooks/usePortalEventBridge.ts`\n */\n\nimport { NextResponse } from 'next/server'\nimport { isPortalBroadcastEvent } from '@open-mercato/shared/modules/events'\nimport { getCustomerAuthFromRequest } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst MAX_PAYLOAD_BYTES = 4096\n\ntype PortalSseConnection = {\n tenantId: string\n organizationId: string\n customerUserId: string\n send: (data: string) => void\n close: () => void\n}\n\nfunction normalizeAudience(data: Record<string, unknown>): {\n tenantId: string | null\n organizationScopes: string[]\n recipientUserScopes: string[]\n} {\n const tenantId = typeof data.tenantId === 'string' ? data.tenantId : null\n const organizationScopes = new Set<string>()\n if (typeof data.organizationId === 'string' && data.organizationId.trim().length > 0) {\n organizationScopes.add(data.organizationId.trim())\n }\n if (Array.isArray(data.organizationIds)) {\n for (const orgId of data.organizationIds) {\n if (typeof orgId === 'string' && orgId.trim().length > 0) {\n organizationScopes.add(orgId.trim())\n }\n }\n }\n\n const recipientUserScopes = new Set<string>()\n if (typeof data.recipientUserId === 'string' && data.recipientUserId.trim().length > 0) {\n recipientUserScopes.add(data.recipientUserId.trim())\n }\n if (Array.isArray(data.recipientUserIds)) {\n for (const userId of data.recipientUserIds) {\n if (typeof userId === 'string' && userId.trim().length > 0) {\n recipientUserScopes.add(userId.trim())\n }\n }\n }\n\n return {\n tenantId,\n organizationScopes: Array.from(organizationScopes),\n recipientUserScopes: Array.from(recipientUserScopes),\n }\n}\n\nfunction matchesAudience(conn: PortalSseConnection, audience: ReturnType<typeof normalizeAudience>): boolean {\n if (!audience.tenantId) return false\n if (conn.tenantId !== audience.tenantId) return false\n if (audience.organizationScopes.length > 0) {\n if (!audience.organizationScopes.includes(conn.organizationId)) return false\n }\n if (audience.recipientUserScopes.length > 0 && !audience.recipientUserScopes.includes(conn.customerUserId)) {\n return false\n }\n return true\n}\n\nconst portalConnections = new Set<PortalSseConnection>()\n\nlet portalTapRegistered = false\n\nasync function broadcastPortalEvent(eventName: string, payload: Record<string, unknown>): Promise<void> {\n if (!eventName || portalConnections.size === 0) return\n if (!isPortalBroadcastEvent(eventName)) return\n\n const data = payload ?? {}\n const audience = normalizeAudience(data)\n const organizationId = audience.organizationScopes[0] ?? ''\n\n let ssePayload = JSON.stringify({\n id: eventName,\n payload: data,\n timestamp: Date.now(),\n organizationId,\n })\n\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n const entityRef: Record<string, unknown> = { truncated: true }\n if (typeof data.id === 'string' && data.id.trim().length > 0) entityRef.id = data.id.trim()\n if (typeof data.entityId === 'string' && data.entityId.trim().length > 0) entityRef.entityId = data.entityId.trim()\n ssePayload = JSON.stringify({\n id: eventName,\n payload: entityRef,\n timestamp: Date.now(),\n organizationId,\n })\n if (new TextEncoder().encode(ssePayload).length > MAX_PAYLOAD_BYTES) {\n return\n }\n }\n\n for (const conn of portalConnections) {\n if (!matchesAudience(conn, audience)) continue\n try {\n conn.send(ssePayload)\n } catch {\n // Connection may have been closed\n }\n }\n}\n\nfunction ensurePortalTap(): void {\n if (portalTapRegistered) return\n portalTapRegistered = true\n\n // Dynamically import to avoid circular dependency \u2014 the events bus\n // registers a global tap that fires for every emitted event.\n import('@open-mercato/events/bus').then(({ registerGlobalEventTap, registerCrossProcessEventListener }) => {\n registerGlobalEventTap(async (eventName, payload) => {\n await broadcastPortalEvent(eventName, (payload ?? {}) as Record<string, unknown>)\n })\n\n registerCrossProcessEventListener(async (envelope) => {\n if (envelope.originPid === process.pid) return\n await broadcastPortalEvent(\n envelope.event,\n (envelope.payload ?? {}) as Record<string, unknown>,\n )\n })\n }).catch(() => {\n // Silently ignore if events package is not available\n portalTapRegistered = false\n })\n}\n\nexport async function GET(req: Request): Promise<Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n ensurePortalTap()\n\n const encoder = new TextEncoder()\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null\n let connection: PortalSseConnection | null = null\n const onAbort = () => cleanup()\n\n const stream = new ReadableStream({\n start(controller) {\n const send = (data: string) => {\n controller.enqueue(encoder.encode(`data: ${data}\\n\\n`))\n }\n\n connection = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n customerUserId: auth.sub,\n send,\n close: () => controller.close(),\n }\n portalConnections.add(connection)\n\n // Flush an initial comment so the runtime sends the response headers and\n // first body byte immediately, firing the browser EventSource `open`\n // event without waiting for the first heartbeat (30s) or matching event.\n // Comment lines (`:` prefix) are ignored by EventSource message parsing.\n controller.enqueue(encoder.encode(': connected\\n\\n'))\n\n heartbeatTimer = setInterval(() => {\n try {\n controller.enqueue(encoder.encode(':heartbeat\\n\\n'))\n } catch {\n // Stream may have been closed\n }\n }, HEARTBEAT_INTERVAL_MS)\n },\n cancel() {\n cleanup()\n },\n })\n\n function cleanup() {\n if (heartbeatTimer) {\n clearInterval(heartbeatTimer)\n heartbeatTimer = null\n }\n if (connection) {\n portalConnections.delete(connection)\n connection = null\n }\n // Detach from the request signal so reconnect churn does not accumulate\n // listeners and closures on long-lived AbortSignals.\n req.signal.removeEventListener('abort', onAbort)\n }\n\n req.signal.addEventListener('abort', onAbort, { once: true })\n\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n })\n}\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Subscribe to portal events via SSE (Portal Event Bridge)',\n description: 'Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer\\'s tenant, organization, and recipient user audience.',\n tags: ['Customer Portal'],\n responses: [\n {\n status: 200,\n description: 'Event stream (text/event-stream)',\n },\n ],\n errors: [\n { status: 401, description: 'Not authenticated' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Portal event stream',\n methods: { GET: methodDoc },\n}\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,kCAAkC;AAGpC,MAAM,WAAqD,EAAE,aAAa,MAAM;AAEvF,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAU1B,SAAS,kBAAkB,MAIzB;AACA,QAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,QAAM,qBAAqB,oBAAI,IAAY;AAC3C,MAAI,OAAO,KAAK,mBAAmB,YAAY,KAAK,eAAe,KAAK,EAAE,SAAS,GAAG;AACpF,uBAAmB,IAAI,KAAK,eAAe,KAAK,CAAC;AAAA,EACnD;AACA,MAAI,MAAM,QAAQ,KAAK,eAAe,GAAG;AACvC,eAAW,SAAS,KAAK,iBAAiB;AACxC,UAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,2BAAmB,IAAI,MAAM,KAAK,CAAC;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAsB,oBAAI,IAAY;AAC5C,MAAI,OAAO,KAAK,oBAAoB,YAAY,KAAK,gBAAgB,KAAK,EAAE,SAAS,GAAG;AACtF,wBAAoB,IAAI,KAAK,gBAAgB,KAAK,CAAC;AAAA,EACrD;AACA,MAAI,MAAM,QAAQ,KAAK,gBAAgB,GAAG;AACxC,eAAW,UAAU,KAAK,kBAAkB;AAC1C,UAAI,OAAO,WAAW,YAAY,OAAO,KAAK,EAAE,SAAS,GAAG;AAC1D,4BAAoB,IAAI,OAAO,KAAK,CAAC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,oBAAoB,MAAM,KAAK,kBAAkB;AAAA,IACjD,qBAAqB,MAAM,KAAK,mBAAmB;AAAA,EACrD;AACF;AAEA,SAAS,gBAAgB,MAA2B,UAAyD;AAC3G,MAAI,CAAC,SAAS,SAAU,QAAO;AAC/B,MAAI,KAAK,aAAa,SAAS,SAAU,QAAO;AAChD,MAAI,SAAS,mBAAmB,SAAS,GAAG;AAC1C,QAAI,CAAC,SAAS,mBAAmB,SAAS,KAAK,cAAc,EAAG,QAAO;AAAA,EACzE;AACA,MAAI,SAAS,oBAAoB,SAAS,KAAK,CAAC,SAAS,oBAAoB,SAAS,KAAK,cAAc,GAAG;AAC1G,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,MAAM,oBAAoB,oBAAI,IAAyB;AAEvD,IAAI,sBAAsB;AAE1B,eAAe,qBAAqB,WAAmB,SAAiD;AACtG,MAAI,CAAC,aAAa,kBAAkB,SAAS,EAAG;AAChD,MAAI,CAAC,uBAAuB,SAAS,EAAG;AAExC,QAAM,OAAO,WAAW,CAAC;AACzB,QAAM,WAAW,kBAAkB,IAAI;AACvC,QAAM,iBAAiB,SAAS,mBAAmB,CAAC,KAAK;AAEzD,MAAI,aAAa,KAAK,UAAU;AAAA,IAC9B,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,WAAW,KAAK,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,IAAI,YAAY,EAAE,OAAO,UAAU,EAAE,SAAS,mBAAmB;AACnE,UAAM,YAAqC,EAAE,WAAW,KAAK;AAC7D,QAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,KAAK,EAAE,SAAS,EAAG,WAAU,KAAK,KAAK,GAAG,KAAK;AAC1F,QAAI,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,EAAE,SAAS,EAAG,WAAU,WAAW,KAAK,SAAS,KAAK;AAClH,iBAAa,KAAK,UAAU;AAAA,MAC1B,IAAI;AAAA,MACJ,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AACD,QAAI,IAAI,YAAY,EAAE,OAAO,UAAU,EAAE,SAAS,mBAAmB;AACnE;AAAA,IACF;AAAA,EACF;AAEA,aAAW,QAAQ,mBAAmB;AACpC,QAAI,CAAC,gBAAgB,MAAM,QAAQ,EAAG;AACtC,QAAI;AACF,WAAK,KAAK,UAAU;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,kBAAwB;AAC/B,MAAI,oBAAqB;AACzB,wBAAsB;AAItB,SAAO,0BAA0B,EAAE,KAAK,CAAC,EAAE,wBAAwB,kCAAkC,MAAM;AACzG,2BAAuB,OAAO,WAAW,YAAY;AACnD,YAAM,qBAAqB,WAAY,WAAW,CAAC,CAA6B;AAAA,IAClF,CAAC;AAED,sCAAkC,OAAO,aAAa;AACpD,UAAI,SAAS,cAAc,QAAQ,IAAK;AACxC,YAAM;AAAA,QACJ,SAAS;AAAA,QACR,SAAS,WAAW,CAAC;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH,CAAC,EAAE,MAAM,MAAM;AAEb,0BAAsB;AAAA,EACxB,CAAC;AACH;AAEA,eAAsB,IAAI,KAAiC;AACzD,QAAM,OAAO,MAAM,2BAA2B,GAAG;AACjD,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AAEA,kBAAgB;AAEhB,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,iBAAwD;AAC5D,MAAI,aAAyC;AAC7C,QAAM,UAAU,MAAM,QAAQ;AAE9B,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,MAAM,YAAY;AAChB,YAAM,OAAO,CAAC,SAAiB;AAC7B,mBAAW,QAAQ,QAAQ,OAAO,SAAS,IAAI;AAAA;AAAA,CAAM,CAAC;AAAA,MACxD;AAEA,mBAAa;AAAA,QACX,UAAU,KAAK;AAAA,QACf,gBAAgB,KAAK;AAAA,QACrB,gBAAgB,KAAK;AAAA,QACrB;AAAA,QACA,OAAO,MAAM,WAAW,MAAM;AAAA,MAChC;AACA,wBAAkB,IAAI,UAAU;AAMhC,iBAAW,QAAQ,QAAQ,OAAO,iBAAiB,CAAC;AAEpD,uBAAiB,YAAY,MAAM;AACjC,YAAI;AACF,qBAAW,QAAQ,QAAQ,OAAO,gBAAgB,CAAC;AAAA,QACrD,QAAQ;AAAA,QAER;AAAA,MACF,GAAG,qBAAqB;AAAA,IAC1B;AAAA,IACA,SAAS;AACP,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AAED,WAAS,UAAU;AACjB,QAAI,gBAAgB;AAClB,oBAAc,cAAc;AAC5B,uBAAiB;AAAA,IACnB;AACA,QAAI,YAAY;AACd,wBAAkB,OAAO,UAAU;AACnC,mBAAa;AAAA,IACf;AAGA,QAAI,OAAO,oBAAoB,SAAS,OAAO;AAAA,EACjD;AAEA,MAAI,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAE5D,SAAO,IAAI,SAAS,QAAQ;AAAA,IAC1B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,qBAAqB;AAAA,IACvB;AAAA,EACF,CAAC;AACH;AAEA,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,iBAAiB;AAAA,EACxB,WAAW;AAAA,IACT;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,EAClD;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS,EAAE,KAAK,UAAU;AAC5B;",
|
|
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.4-develop.
|
|
3
|
+
"version": "0.6.4-develop.4264.1.53368d85fe",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -243,16 +243,16 @@
|
|
|
243
243
|
"zod": "^4.4.3"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
247
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
248
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
246
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4264.1.53368d85fe",
|
|
247
|
+
"@open-mercato/shared": "0.6.4-develop.4264.1.53368d85fe",
|
|
248
|
+
"@open-mercato/ui": "0.6.4-develop.4264.1.53368d85fe",
|
|
249
249
|
"react": "^19.0.0",
|
|
250
250
|
"react-dom": "^19.0.0"
|
|
251
251
|
},
|
|
252
252
|
"devDependencies": {
|
|
253
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
254
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
255
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
253
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4264.1.53368d85fe",
|
|
254
|
+
"@open-mercato/shared": "0.6.4-develop.4264.1.53368d85fe",
|
|
255
|
+
"@open-mercato/ui": "0.6.4-develop.4264.1.53368d85fe",
|
|
256
256
|
"@testing-library/dom": "^10.4.1",
|
|
257
257
|
"@testing-library/jest-dom": "^6.9.1",
|
|
258
258
|
"@testing-library/react": "^16.3.1",
|
|
@@ -173,6 +173,12 @@ export async function GET(req: Request): Promise<Response> {
|
|
|
173
173
|
}
|
|
174
174
|
portalConnections.add(connection)
|
|
175
175
|
|
|
176
|
+
// Flush an initial comment so the runtime sends the response headers and
|
|
177
|
+
// first body byte immediately, firing the browser EventSource `open`
|
|
178
|
+
// event without waiting for the first heartbeat (30s) or matching event.
|
|
179
|
+
// Comment lines (`:` prefix) are ignored by EventSource message parsing.
|
|
180
|
+
controller.enqueue(encoder.encode(': connected\n\n'))
|
|
181
|
+
|
|
176
182
|
heartbeatTimer = setInterval(() => {
|
|
177
183
|
try {
|
|
178
184
|
controller.enqueue(encoder.encode(':heartbeat\n\n'))
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"staff.audit.teams.create": "Team erstellen",
|
|
119
119
|
"staff.audit.teams.delete": "Team löschen",
|
|
120
120
|
"staff.audit.teams.update": "Team aktualisieren",
|
|
121
|
+
"staff.audit.timesheets.time_entries.create": "Zeiteintrag erstellen",
|
|
122
|
+
"staff.audit.timesheets.time_entries.delete": "Zeiteintrag löschen",
|
|
123
|
+
"staff.audit.timesheets.time_entries.update": "Zeiteintrag aktualisieren",
|
|
124
|
+
"staff.audit.timesheets.time_project_members.assign": "Zeitprojektmitglied zuweisen",
|
|
125
|
+
"staff.audit.timesheets.time_project_members.unassign": "Zeitprojektmitglied entfernen",
|
|
126
|
+
"staff.audit.timesheets.time_projects.create": "Zeitprojekt erstellen",
|
|
127
|
+
"staff.audit.timesheets.time_projects.delete": "Zeitprojekt löschen",
|
|
128
|
+
"staff.audit.timesheets.time_projects.update": "Zeitprojekt aktualisieren",
|
|
121
129
|
"staff.availability.errors.organizationRequired": "Organisationskontext ist erforderlich.",
|
|
122
130
|
"staff.availability.errors.unauthorized": "Nicht autorisiert",
|
|
123
131
|
"staff.availability.errors.updateDateSpecific": "Datumsspezifische Verfügbarkeit konnte nicht gespeichert werden.",
|
|
@@ -202,6 +210,7 @@
|
|
|
202
210
|
"staff.availabilityRuleSets.tabs.availability": "Verfügbarkeit",
|
|
203
211
|
"staff.availabilityRuleSets.tabs.details": "Details",
|
|
204
212
|
"staff.availabilityRuleSets.tabs.label": "Zeitplanbereiche",
|
|
213
|
+
"staff.errors.missingScope": "Mandanten- oder Organisationskontext fehlt.",
|
|
205
214
|
"staff.errors.unauthorized": "Nicht autorisiert",
|
|
206
215
|
"staff.leaveRequests.actions.accept": "Genehmigen",
|
|
207
216
|
"staff.leaveRequests.actions.add": "Neuer Antrag",
|
|
@@ -413,6 +422,7 @@
|
|
|
413
422
|
"staff.search.badge.team": "Team",
|
|
414
423
|
"staff.search.badge.teamMember": "Teammitglied",
|
|
415
424
|
"staff.search.badge.teamRole": "Teamrolle",
|
|
425
|
+
"staff.search.badge.timeProject": "Projekt",
|
|
416
426
|
"staff.search.service.maxAttendees": "Max. {{count}}",
|
|
417
427
|
"staff.search.status.active": "Aktiv",
|
|
418
428
|
"staff.search.status.inactive": "Inaktiv",
|
|
@@ -918,13 +928,26 @@
|
|
|
918
928
|
"staff.teams.tabs.details": "Details",
|
|
919
929
|
"staff.teams.tabs.label": "Teamabschnitte",
|
|
920
930
|
"staff.teams.tabs.members": "Teammitglieder",
|
|
931
|
+
"staff.timesheets.errors.bulkSave": "Zeiteinträge konnten nicht im Stapel gespeichert werden.",
|
|
921
932
|
"staff.timesheets.errors.entryNotFound": "Zeiteintrag nicht gefunden, gelöscht oder Ihnen nicht zugeordnet.",
|
|
933
|
+
"staff.timesheets.errors.invalidBody": "Ungültiger Anfragetext.",
|
|
934
|
+
"staff.timesheets.errors.invalidProjectId": "Ungültige Projekt-ID.",
|
|
935
|
+
"staff.timesheets.errors.memberRequired": "Die ID des Zeitprojektmitglieds ist erforderlich.",
|
|
936
|
+
"staff.timesheets.errors.missingEntryId": "Eintrags-ID fehlt.",
|
|
937
|
+
"staff.timesheets.errors.myProjects": "Ihre Projekte konnten nicht geladen werden.",
|
|
938
|
+
"staff.timesheets.errors.noActiveSegment": "Für diesen Eintrag wurde kein aktives Timer-Segment gefunden.",
|
|
922
939
|
"staff.timesheets.errors.noStaffMember": "Ihrem Konto ist kein Mitarbeiter zugeordnet.",
|
|
940
|
+
"staff.timesheets.errors.notAssigned": "Sie sind diesem Projekt nicht zugewiesen.",
|
|
923
941
|
"staff.timesheets.errors.notOwner": "Sie können nur Ihre eigenen Zeiteinträge verwalten.",
|
|
924
942
|
"staff.timesheets.errors.projectCodeDuplicate": "Ein Projekt mit diesem Code existiert bereits.",
|
|
925
943
|
"staff.timesheets.errors.projectNotFound": "Zeitprojekt nicht gefunden oder nicht zugänglich.",
|
|
926
944
|
"staff.timesheets.errors.projectsKpis": "Failed to load project KPIs.",
|
|
945
|
+
"staff.timesheets.errors.segmentCreate": "Zeiteintragssegment konnte nicht erstellt werden.",
|
|
927
946
|
"staff.timesheets.errors.staffMemberNotFound": "Mitarbeiter nicht gefunden oder nicht zugänglich.",
|
|
947
|
+
"staff.timesheets.errors.timerAlreadyStarted": "Der Timer ist für diesen Eintrag bereits gestartet.",
|
|
948
|
+
"staff.timesheets.errors.timerStart": "Timer konnte nicht gestartet werden.",
|
|
949
|
+
"staff.timesheets.errors.timerStop": "Timer konnte nicht gestoppt werden.",
|
|
950
|
+
"staff.timesheets.errors.updateMyProject": "Projektsichtbarkeit konnte nicht aktualisiert werden.",
|
|
928
951
|
"staff.timesheets.my.addRow.createProject": "Create a new project",
|
|
929
952
|
"staff.timesheets.my.addRow.error": "Das Projekt konnte nicht hinzugefügt werden. Bitte erneut versuchen.",
|
|
930
953
|
"staff.timesheets.my.addRow.noProjects": "No projects assigned",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"staff.audit.teams.create": "Create team",
|
|
119
119
|
"staff.audit.teams.delete": "Delete team",
|
|
120
120
|
"staff.audit.teams.update": "Update team",
|
|
121
|
+
"staff.audit.timesheets.time_entries.create": "Create time entry",
|
|
122
|
+
"staff.audit.timesheets.time_entries.delete": "Delete time entry",
|
|
123
|
+
"staff.audit.timesheets.time_entries.update": "Update time entry",
|
|
124
|
+
"staff.audit.timesheets.time_project_members.assign": "Assign time project member",
|
|
125
|
+
"staff.audit.timesheets.time_project_members.unassign": "Unassign time project member",
|
|
126
|
+
"staff.audit.timesheets.time_projects.create": "Create time project",
|
|
127
|
+
"staff.audit.timesheets.time_projects.delete": "Delete time project",
|
|
128
|
+
"staff.audit.timesheets.time_projects.update": "Update time project",
|
|
121
129
|
"staff.availability.errors.organizationRequired": "Organization context is required.",
|
|
122
130
|
"staff.availability.errors.unauthorized": "Unauthorized",
|
|
123
131
|
"staff.availability.errors.updateDateSpecific": "Failed to save date-specific availability.",
|
|
@@ -202,6 +210,7 @@
|
|
|
202
210
|
"staff.availabilityRuleSets.tabs.availability": "Availability",
|
|
203
211
|
"staff.availabilityRuleSets.tabs.details": "Details",
|
|
204
212
|
"staff.availabilityRuleSets.tabs.label": "Schedule sections",
|
|
213
|
+
"staff.errors.missingScope": "Missing tenant or organization scope.",
|
|
205
214
|
"staff.errors.unauthorized": "Unauthorized",
|
|
206
215
|
"staff.leaveRequests.actions.accept": "Approve",
|
|
207
216
|
"staff.leaveRequests.actions.add": "New request",
|
|
@@ -413,6 +422,7 @@
|
|
|
413
422
|
"staff.search.badge.team": "Team",
|
|
414
423
|
"staff.search.badge.teamMember": "Team member",
|
|
415
424
|
"staff.search.badge.teamRole": "Team role",
|
|
425
|
+
"staff.search.badge.timeProject": "Project",
|
|
416
426
|
"staff.search.service.maxAttendees": "Max {{count}}",
|
|
417
427
|
"staff.search.status.active": "Active",
|
|
418
428
|
"staff.search.status.inactive": "Inactive",
|
|
@@ -918,13 +928,26 @@
|
|
|
918
928
|
"staff.teams.tabs.details": "Details",
|
|
919
929
|
"staff.teams.tabs.label": "Team sections",
|
|
920
930
|
"staff.teams.tabs.members": "Team members",
|
|
931
|
+
"staff.timesheets.errors.bulkSave": "Failed to bulk save time entries.",
|
|
921
932
|
"staff.timesheets.errors.entryNotFound": "Time entry not found, deleted, or not owned by you.",
|
|
933
|
+
"staff.timesheets.errors.invalidBody": "Invalid request body.",
|
|
934
|
+
"staff.timesheets.errors.invalidProjectId": "Invalid project id.",
|
|
935
|
+
"staff.timesheets.errors.memberRequired": "Time project member id is required.",
|
|
936
|
+
"staff.timesheets.errors.missingEntryId": "Missing entry ID.",
|
|
937
|
+
"staff.timesheets.errors.myProjects": "Failed to load your projects.",
|
|
938
|
+
"staff.timesheets.errors.noActiveSegment": "No active timer segment found for this entry.",
|
|
922
939
|
"staff.timesheets.errors.noStaffMember": "No staff member linked to your account.",
|
|
940
|
+
"staff.timesheets.errors.notAssigned": "You are not assigned to this project.",
|
|
923
941
|
"staff.timesheets.errors.notOwner": "You can only manage your own time entries.",
|
|
924
942
|
"staff.timesheets.errors.projectCodeDuplicate": "A project with this code already exists.",
|
|
925
943
|
"staff.timesheets.errors.projectNotFound": "Time project not found or not accessible.",
|
|
926
944
|
"staff.timesheets.errors.projectsKpis": "Failed to load project KPIs.",
|
|
945
|
+
"staff.timesheets.errors.segmentCreate": "Failed to create time entry segment.",
|
|
927
946
|
"staff.timesheets.errors.staffMemberNotFound": "Staff member not found or not accessible.",
|
|
947
|
+
"staff.timesheets.errors.timerAlreadyStarted": "Timer is already started for this entry.",
|
|
948
|
+
"staff.timesheets.errors.timerStart": "Failed to start timer.",
|
|
949
|
+
"staff.timesheets.errors.timerStop": "Failed to stop timer.",
|
|
950
|
+
"staff.timesheets.errors.updateMyProject": "Failed to update project visibility.",
|
|
928
951
|
"staff.timesheets.my.addRow.createProject": "Create a new project",
|
|
929
952
|
"staff.timesheets.my.addRow.error": "Could not add the project. Please try again.",
|
|
930
953
|
"staff.timesheets.my.addRow.noProjects": "No projects assigned",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"staff.audit.teams.create": "Crear equipo",
|
|
119
119
|
"staff.audit.teams.delete": "Eliminar equipo",
|
|
120
120
|
"staff.audit.teams.update": "Actualizar equipo",
|
|
121
|
+
"staff.audit.timesheets.time_entries.create": "Crear registro de tiempo",
|
|
122
|
+
"staff.audit.timesheets.time_entries.delete": "Eliminar registro de tiempo",
|
|
123
|
+
"staff.audit.timesheets.time_entries.update": "Actualizar registro de tiempo",
|
|
124
|
+
"staff.audit.timesheets.time_project_members.assign": "Asignar miembro del proyecto de tiempo",
|
|
125
|
+
"staff.audit.timesheets.time_project_members.unassign": "Desasignar miembro del proyecto de tiempo",
|
|
126
|
+
"staff.audit.timesheets.time_projects.create": "Crear proyecto de tiempo",
|
|
127
|
+
"staff.audit.timesheets.time_projects.delete": "Eliminar proyecto de tiempo",
|
|
128
|
+
"staff.audit.timesheets.time_projects.update": "Actualizar proyecto de tiempo",
|
|
121
129
|
"staff.availability.errors.organizationRequired": "Se requiere el contexto de la organización.",
|
|
122
130
|
"staff.availability.errors.unauthorized": "No autorizado",
|
|
123
131
|
"staff.availability.errors.updateDateSpecific": "No se pudo guardar la disponibilidad por fecha.",
|
|
@@ -202,6 +210,7 @@
|
|
|
202
210
|
"staff.availabilityRuleSets.tabs.availability": "Disponibilidad",
|
|
203
211
|
"staff.availabilityRuleSets.tabs.details": "Detalles",
|
|
204
212
|
"staff.availabilityRuleSets.tabs.label": "Secciones del horario",
|
|
213
|
+
"staff.errors.missingScope": "Falta el contexto de inquilino u organización.",
|
|
205
214
|
"staff.errors.unauthorized": "No autorizado",
|
|
206
215
|
"staff.leaveRequests.actions.accept": "Aprobar",
|
|
207
216
|
"staff.leaveRequests.actions.add": "Nueva solicitud",
|
|
@@ -413,6 +422,7 @@
|
|
|
413
422
|
"staff.search.badge.team": "Equipo",
|
|
414
423
|
"staff.search.badge.teamMember": "Miembro del equipo",
|
|
415
424
|
"staff.search.badge.teamRole": "Rol del equipo",
|
|
425
|
+
"staff.search.badge.timeProject": "Proyecto",
|
|
416
426
|
"staff.search.service.maxAttendees": "Máx. {{count}}",
|
|
417
427
|
"staff.search.status.active": "Activo",
|
|
418
428
|
"staff.search.status.inactive": "Inactivo",
|
|
@@ -918,13 +928,26 @@
|
|
|
918
928
|
"staff.teams.tabs.details": "Detalles",
|
|
919
929
|
"staff.teams.tabs.label": "Secciones del equipo",
|
|
920
930
|
"staff.teams.tabs.members": "Miembros del equipo",
|
|
931
|
+
"staff.timesheets.errors.bulkSave": "No se pudieron guardar en lote los registros de tiempo.",
|
|
921
932
|
"staff.timesheets.errors.entryNotFound": "Registro de tiempo no encontrado, eliminado o no asignado a ti.",
|
|
933
|
+
"staff.timesheets.errors.invalidBody": "Cuerpo de la solicitud no válido.",
|
|
934
|
+
"staff.timesheets.errors.invalidProjectId": "Identificador de proyecto no válido.",
|
|
935
|
+
"staff.timesheets.errors.memberRequired": "El identificador del miembro del proyecto de tiempo es obligatorio.",
|
|
936
|
+
"staff.timesheets.errors.missingEntryId": "Falta el identificador del registro.",
|
|
937
|
+
"staff.timesheets.errors.myProjects": "No se pudieron cargar tus proyectos.",
|
|
938
|
+
"staff.timesheets.errors.noActiveSegment": "No se encontró ningún segmento de temporizador activo para este registro.",
|
|
922
939
|
"staff.timesheets.errors.noStaffMember": "Ningún miembro del personal vinculado a tu cuenta.",
|
|
940
|
+
"staff.timesheets.errors.notAssigned": "No estás asignado a este proyecto.",
|
|
923
941
|
"staff.timesheets.errors.notOwner": "Solo puedes gestionar tus propios registros de tiempo.",
|
|
924
942
|
"staff.timesheets.errors.projectCodeDuplicate": "Ya existe un proyecto con este código.",
|
|
925
943
|
"staff.timesheets.errors.projectNotFound": "Proyecto de tiempo no encontrado o no accesible.",
|
|
926
944
|
"staff.timesheets.errors.projectsKpis": "Failed to load project KPIs.",
|
|
945
|
+
"staff.timesheets.errors.segmentCreate": "No se pudo crear el segmento del registro de tiempo.",
|
|
927
946
|
"staff.timesheets.errors.staffMemberNotFound": "Miembro del personal no encontrado o no accesible.",
|
|
947
|
+
"staff.timesheets.errors.timerAlreadyStarted": "El temporizador ya está iniciado para este registro.",
|
|
948
|
+
"staff.timesheets.errors.timerStart": "No se pudo iniciar el temporizador.",
|
|
949
|
+
"staff.timesheets.errors.timerStop": "No se pudo detener el temporizador.",
|
|
950
|
+
"staff.timesheets.errors.updateMyProject": "No se pudo actualizar la visibilidad del proyecto.",
|
|
928
951
|
"staff.timesheets.my.addRow.createProject": "Create a new project",
|
|
929
952
|
"staff.timesheets.my.addRow.error": "No se pudo añadir el proyecto. Inténtalo de nuevo.",
|
|
930
953
|
"staff.timesheets.my.addRow.noProjects": "No projects assigned",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"staff.audit.teams.create": "Utwórz zespół",
|
|
119
119
|
"staff.audit.teams.delete": "Usuń zespół",
|
|
120
120
|
"staff.audit.teams.update": "Zaktualizuj zespół",
|
|
121
|
+
"staff.audit.timesheets.time_entries.create": "Utwórz wpis czasu",
|
|
122
|
+
"staff.audit.timesheets.time_entries.delete": "Usuń wpis czasu",
|
|
123
|
+
"staff.audit.timesheets.time_entries.update": "Zaktualizuj wpis czasu",
|
|
124
|
+
"staff.audit.timesheets.time_project_members.assign": "Przypisz członka projektu czasu",
|
|
125
|
+
"staff.audit.timesheets.time_project_members.unassign": "Usuń przypisanie członka projektu czasu",
|
|
126
|
+
"staff.audit.timesheets.time_projects.create": "Utwórz projekt czasu",
|
|
127
|
+
"staff.audit.timesheets.time_projects.delete": "Usuń projekt czasu",
|
|
128
|
+
"staff.audit.timesheets.time_projects.update": "Zaktualizuj projekt czasu",
|
|
121
129
|
"staff.availability.errors.organizationRequired": "Wymagany jest kontekst organizacji.",
|
|
122
130
|
"staff.availability.errors.unauthorized": "Brak autoryzacji",
|
|
123
131
|
"staff.availability.errors.updateDateSpecific": "Nie udało się zapisać dostępności dla dat.",
|
|
@@ -202,6 +210,7 @@
|
|
|
202
210
|
"staff.availabilityRuleSets.tabs.availability": "Dostępność",
|
|
203
211
|
"staff.availabilityRuleSets.tabs.details": "Szczegóły",
|
|
204
212
|
"staff.availabilityRuleSets.tabs.label": "Sekcje harmonogramu",
|
|
213
|
+
"staff.errors.missingScope": "Brak kontekstu najemcy lub organizacji.",
|
|
205
214
|
"staff.errors.unauthorized": "Brak autoryzacji",
|
|
206
215
|
"staff.leaveRequests.actions.accept": "Zatwierdź",
|
|
207
216
|
"staff.leaveRequests.actions.add": "Nowy wniosek",
|
|
@@ -413,6 +422,7 @@
|
|
|
413
422
|
"staff.search.badge.team": "Zespół",
|
|
414
423
|
"staff.search.badge.teamMember": "Członek zespołu",
|
|
415
424
|
"staff.search.badge.teamRole": "Rola zespołu",
|
|
425
|
+
"staff.search.badge.timeProject": "Projekt",
|
|
416
426
|
"staff.search.service.maxAttendees": "Maks. {{count}}",
|
|
417
427
|
"staff.search.status.active": "Aktywny",
|
|
418
428
|
"staff.search.status.inactive": "Nieaktywny",
|
|
@@ -918,13 +928,26 @@
|
|
|
918
928
|
"staff.teams.tabs.details": "Szczegóły",
|
|
919
929
|
"staff.teams.tabs.label": "Sekcje zespołu",
|
|
920
930
|
"staff.teams.tabs.members": "Członkowie zespołu",
|
|
931
|
+
"staff.timesheets.errors.bulkSave": "Nie udało się zbiorczo zapisać wpisów czasu.",
|
|
921
932
|
"staff.timesheets.errors.entryNotFound": "Wpis czasu nie istnieje, został usunięty lub nie należy do Ciebie.",
|
|
933
|
+
"staff.timesheets.errors.invalidBody": "Nieprawidłowe ciało żądania.",
|
|
934
|
+
"staff.timesheets.errors.invalidProjectId": "Nieprawidłowy identyfikator projektu.",
|
|
935
|
+
"staff.timesheets.errors.memberRequired": "Identyfikator członka projektu czasu jest wymagany.",
|
|
936
|
+
"staff.timesheets.errors.missingEntryId": "Brak identyfikatora wpisu.",
|
|
937
|
+
"staff.timesheets.errors.myProjects": "Nie udało się załadować Twoich projektów.",
|
|
938
|
+
"staff.timesheets.errors.noActiveSegment": "Nie znaleziono aktywnego segmentu czasomierza dla tego wpisu.",
|
|
922
939
|
"staff.timesheets.errors.noStaffMember": "Brak pracownika powiązanego z Twoim kontem.",
|
|
940
|
+
"staff.timesheets.errors.notAssigned": "Nie jesteś przypisany do tego projektu.",
|
|
923
941
|
"staff.timesheets.errors.notOwner": "Możesz zarządzać tylko własnymi wpisami czasu.",
|
|
924
942
|
"staff.timesheets.errors.projectCodeDuplicate": "Projekt o tym kodzie już istnieje.",
|
|
925
943
|
"staff.timesheets.errors.projectNotFound": "Projekt czasowy nie został znaleziony lub jest niedostępny.",
|
|
926
944
|
"staff.timesheets.errors.projectsKpis": "Failed to load project KPIs.",
|
|
945
|
+
"staff.timesheets.errors.segmentCreate": "Nie udało się utworzyć segmentu wpisu czasu.",
|
|
927
946
|
"staff.timesheets.errors.staffMemberNotFound": "Pracownik nie został znaleziony lub jest niedostępny.",
|
|
947
|
+
"staff.timesheets.errors.timerAlreadyStarted": "Czasomierz jest już uruchomiony dla tego wpisu.",
|
|
948
|
+
"staff.timesheets.errors.timerStart": "Nie udało się uruchomić czasomierza.",
|
|
949
|
+
"staff.timesheets.errors.timerStop": "Nie udało się zatrzymać czasomierza.",
|
|
950
|
+
"staff.timesheets.errors.updateMyProject": "Nie udało się zaktualizować widoczności projektu.",
|
|
928
951
|
"staff.timesheets.my.addRow.createProject": "Create a new project",
|
|
929
952
|
"staff.timesheets.my.addRow.error": "Nie udało się dodać projektu. Spróbuj ponownie.",
|
|
930
953
|
"staff.timesheets.my.addRow.noProjects": "No projects assigned",
|