@flamingo-stack/openframe-frontend-core 0.0.716 → 0.0.718
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/chat-protocol/index.cjs +23 -14
- package/dist/chat-protocol/index.js +23 -14
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/chat-protocol/source-metadata.d.ts +11 -2
- package/dist/chat-protocol/source-metadata.d.ts.map +1 -1
- package/dist/{chunk-DPX6244M.js → chunk-45F7PXUI.js} +3 -3
- package/dist/{chunk-KYFNFRSV.js → chunk-67QBMN4R.js} +2 -2
- package/dist/{chunk-3RKBJT6K.cjs → chunk-6A43ZDWV.cjs} +2 -2
- package/dist/chunk-A6P6DHEZ.cjs +130 -0
- package/dist/{chunk-KNOZVVCU.cjs → chunk-BOTM373R.cjs} +9 -9
- package/dist/{chunk-DHMO5GI4.cjs → chunk-BWIN42ZA.cjs} +21 -21
- package/dist/{chunk-YTMV4OBY.js → chunk-DCD5WZUF.js} +4 -4
- package/dist/{chunk-V36WGIYR.js → chunk-DEZDMTBM.js} +5 -5
- package/dist/{chunk-YO5JB3WE.cjs → chunk-DXCBOZVT.cjs} +35 -35
- package/dist/{chunk-ZX7SCPLO.cjs → chunk-E7YCPY7K.cjs} +3 -3
- package/dist/{chunk-I5F2OTFE.cjs → chunk-EZC2F5OT.cjs} +33 -33
- package/dist/{chunk-DLEO53NX.cjs → chunk-GN4A7PMB.cjs} +69 -69
- package/dist/{chunk-6T3BTD6Q.js → chunk-IWCPXJEE.js} +201 -327
- package/dist/{chunk-ROBQ5TR7.js → chunk-JH2SIWPQ.js} +28 -17
- package/dist/{chunk-3AJIQPFG.js → chunk-JIWMBIVZ.js} +6 -6
- package/dist/{chunk-DDWFCWOE.js → chunk-JPD4NXH6.js} +1 -1
- package/dist/{chunk-JVCAK4W3.cjs → chunk-LE5WKGJV.cjs} +3 -3
- package/dist/{chunk-NPAQSMRY.js → chunk-LOYPRJWA.js} +1053 -357
- package/dist/{chunk-5IJXM3RU.cjs → chunk-MIQLUUVF.cjs} +10 -10
- package/dist/{chunk-MXPQMQI3.js → chunk-MM7MCIXL.js} +6 -6
- package/dist/{chunk-C5AGSZBT.js → chunk-N2PF5CFX.js} +4837 -5425
- package/dist/{chunk-STCJYO2Z.cjs → chunk-NDZ5ZXOQ.cjs} +11 -11
- package/dist/{chunk-H5R7U2D2.js → chunk-NZ5BHDFZ.js} +1 -1
- package/dist/{chunk-JCA5MBVY.js → chunk-PQ6HBLZ7.js} +4 -4
- package/dist/{chunk-EOKSKJSS.cjs → chunk-QBCDSTLE.cjs} +13 -13
- package/dist/{chunk-NQWJAOHO.js → chunk-QXZCLQNV.js} +1 -1
- package/dist/{chunk-2V7D5GJX.cjs → chunk-S2FUFMLW.cjs} +25 -25
- package/dist/{chunk-64F6OEEV.js → chunk-SGFTTJKO.js} +1 -1
- package/dist/{chunk-P7IL4CX3.js → chunk-TBCZCRQ5.js} +6 -6
- package/dist/{chunk-HETQETPV.cjs → chunk-UWQWMGJ7.cjs} +28 -17
- package/dist/{chunk-X2MNNV5H.cjs → chunk-WALCXC7J.cjs} +3 -3
- package/dist/{chunk-YBS5QGON.js → chunk-XGHGGBM3.js} +2 -2
- package/dist/{chunk-NKILEG3Y.cjs → chunk-YC5COB5V.cjs} +45 -45
- package/dist/{chunk-BVZ7W4HW.js → chunk-YEMCCYIF.js} +4 -4
- package/dist/chunk-YYYVS7CX.js +130 -0
- package/dist/{chunk-S2VWQX6T.cjs → chunk-Z3CVSPO3.cjs} +842 -1430
- package/dist/{chunk-PPXR22YC.cjs → chunk-Z5UWW3Z2.cjs} +707 -11
- package/dist/{chunk-JO5TLJPM.cjs → chunk-ZXCZYMN4.cjs} +37 -163
- package/dist/{cloud-tenants-2J7CPTKT.cjs → cloud-tenants-PFDF7O5U.cjs} +6 -5
- package/dist/{cloud-tenants-YOY2WLGC.js → cloud-tenants-TAKOWXLD.js} +5 -4
- package/dist/components/case-studies/index.cjs +17 -16
- package/dist/components/case-studies/index.js +6 -5
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/message.types.d.ts +20 -16
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +7 -6
- package/dist/components/contact/index.js +6 -5
- package/dist/components/docs/index.cjs +10 -9
- package/dist/components/docs/index.js +9 -8
- package/dist/components/embeds/index.cjs +7 -6
- package/dist/components/embeds/index.js +6 -5
- package/dist/components/faq/index.cjs +7 -6
- package/dist/components/faq/index.js +6 -5
- package/dist/components/features/index.cjs +10 -5
- package/dist/components/features/index.js +9 -4
- package/dist/components/features/remote-session/index.d.ts +2 -0
- package/dist/components/features/remote-session/index.d.ts.map +1 -1
- package/dist/components/features/remote-session/remote-session-keep-modals.d.ts +3 -3
- package/dist/components/features/remote-session/remote-session-keep-modals.d.ts.map +1 -1
- package/dist/components/features/remote-session/remote-session-ticket-modals.d.ts +29 -0
- package/dist/components/features/remote-session/remote-session-ticket-modals.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +71 -70
- package/dist/components/help-center-pages/index.js +17 -16
- package/dist/components/index.cjs +44 -39
- package/dist/components/index.js +22 -17
- package/dist/components/meeting-scheduler/index.cjs +33 -32
- package/dist/components/meeting-scheduler/index.js +5 -4
- package/dist/components/navigation/index.cjs +9 -8
- package/dist/components/navigation/index.js +8 -7
- package/dist/components/onboarding-guides/index.cjs +9 -8
- package/dist/components/onboarding-guides/index.js +8 -7
- package/dist/components/product-demo/index.cjs +7 -7
- package/dist/components/product-demo/index.js +7 -7
- package/dist/components/tickets/index.cjs +10 -9
- package/dist/components/tickets/index.js +9 -8
- package/dist/components/ui/autocomplete.d.ts +6 -0
- package/dist/components/ui/autocomplete.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +8 -7
- package/dist/components/ui/index.js +7 -6
- package/dist/{devices-5NADSKDZ.cjs → devices-KRCGZMGA.cjs} +7 -6
- package/dist/{devices-EQXXILXW.js → devices-LHEX7O5T.js} +5 -4
- package/dist/hooks/index.cjs +6 -4
- package/dist/hooks/index.js +5 -3
- package/dist/index.cjs +17 -11
- package/dist/index.js +18 -12
- package/dist/{knowledge-LHQYZJE7.cjs → knowledge-KLVPVEYZ.cjs} +7 -6
- package/dist/{knowledge-YCQITBJB.js → knowledge-RZSFNR33.js} +5 -4
- package/dist/{logs-LZNXGOSW.js → logs-E5DOV5JO.js} +5 -4
- package/dist/{logs-DUVKSQU3.cjs → logs-OX3XTVL2.cjs} +14 -13
- package/dist/{policies-5H74FS4S.cjs → policies-FOSCIZGK.cjs} +10 -9
- package/dist/{policies-VYWOJ7IF.js → policies-MC2S4S5G.js} +5 -4
- package/dist/{remote-session-R6VAFESR.cjs → remote-session-DRFE5XZE.cjs} +4 -3
- package/dist/{remote-session-GSGZOFDX.js → remote-session-JQ2BPIE3.js} +2 -1
- package/dist/{software-update-MSDY3OLX.js → software-update-D4OEIH65.js} +7 -5
- package/dist/{software-update-VRRX5U4W.cjs → software-update-HOBSTWVO.cjs} +12 -10
- package/package.json +1 -1
- package/src/chat-protocol/nats-decoder.ts +10 -8
- package/src/chat-protocol/source-metadata.ts +25 -2
- package/src/components/chat/types/message.types.ts +21 -15
- package/src/components/chat/types/unified-chat-state.types.ts +1 -1
- package/src/components/chat/utils/process-historical-messages.ts +12 -4
- package/src/components/features/remote-session/index.ts +2 -0
- package/src/components/features/remote-session/remote-session-keep-modals.tsx +4 -4
- package/src/components/features/remote-session/remote-session-ticket-modals.tsx +140 -0
- package/src/components/ui/autocomplete.tsx +18 -4
|
@@ -12,15 +12,13 @@ export const MESSAGE_TYPE = {
|
|
|
12
12
|
TEXT: 'TEXT',
|
|
13
13
|
THINKING: 'THINKING',
|
|
14
14
|
ASK: 'ASK',
|
|
15
|
-
/** Per-answer
|
|
15
|
+
/** Per-answer attachments (cited documents, videos, entity cards).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
* deletion here and nothing else. */
|
|
17
|
+
* `ATTACHMENTS` carries `sources` / `videos` / `cards` on the record itself.
|
|
18
|
+
* `GUIDE` is the envelope it replaced — the same arrays under `payload` —
|
|
19
|
+
* and stays only until every backend ships `ATTACHMENTS`. */
|
|
20
|
+
ATTACHMENTS: 'ATTACHMENTS',
|
|
22
21
|
GUIDE: 'GUIDE',
|
|
23
|
-
SOURCES: 'SOURCES',
|
|
24
22
|
EXECUTING_TOOL: 'EXECUTING_TOOL',
|
|
25
23
|
EXECUTED_TOOL: 'EXECUTED_TOOL',
|
|
26
24
|
APPROVAL_REQUEST: 'APPROVAL_REQUEST',
|
|
@@ -412,7 +410,7 @@ export interface ThinkingMessageData extends MessageDataBase {
|
|
|
412
410
|
*
|
|
413
411
|
* Lives here, beside `Message`, because BOTH transports produce it now: the SSE
|
|
414
412
|
* adapter reads it off the per-turn metadata frame, and the NATS path decodes it
|
|
415
|
-
* out of
|
|
413
|
+
* out of an `ATTACHMENTS` chunk. It used to be declared inside
|
|
416
414
|
* `use-sse-chat-adapter`, which made "a Guide answer's citations" structurally a
|
|
417
415
|
* property of one transport — `use-sse-chat-adapter` still re-exports the name
|
|
418
416
|
* so existing imports keep working.
|
|
@@ -451,13 +449,20 @@ export interface ChatSource {
|
|
|
451
449
|
label?: string;
|
|
452
450
|
}
|
|
453
451
|
|
|
454
|
-
/** Persisted
|
|
455
|
-
* carries, so history and realtime share one decoder
|
|
456
|
-
* and cannot drift apart. Left
|
|
457
|
-
*
|
|
458
|
-
|
|
452
|
+
/** Persisted attachments row (GraphQL `AttachmentsData`). The arrays are the
|
|
453
|
+
* SAME ones the live chunk carries, so history and realtime share one decoder
|
|
454
|
+
* (`attachmentsEvent`) and cannot drift apart. Left untyped: it is unvalidated
|
|
455
|
+
* wire data, and the decoder is what turns it into `ChatSource[]` / `ChatRef[]`. */
|
|
456
|
+
export interface AttachmentsMessageData extends MessageDataBase {
|
|
457
|
+
type: 'ATTACHMENTS';
|
|
458
|
+
sources?: unknown;
|
|
459
|
+
videos?: unknown;
|
|
460
|
+
cards?: unknown;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** The envelope `ATTACHMENTS` replaced: the same arrays under `payload`. */
|
|
459
464
|
export interface SourceMetadataMessageData extends MessageDataBase {
|
|
460
|
-
type: 'GUIDE'
|
|
465
|
+
type: 'GUIDE';
|
|
461
466
|
payload?: unknown;
|
|
462
467
|
}
|
|
463
468
|
|
|
@@ -603,6 +608,7 @@ export type MessageData =
|
|
|
603
608
|
| TextMessageData
|
|
604
609
|
| ThinkingMessageData
|
|
605
610
|
| AskMessageData
|
|
611
|
+
| AttachmentsMessageData
|
|
606
612
|
| SourceMetadataMessageData
|
|
607
613
|
| ExecutingToolMessageData
|
|
608
614
|
| ExecutedToolMessageData
|
|
@@ -654,7 +660,7 @@ export interface ProcessedMessage {
|
|
|
654
660
|
* Absent when the source row(s) carried no seq. */
|
|
655
661
|
streamSeq?: number;
|
|
656
662
|
/** Documents this answer cited — see `Message.sources`, the same field.
|
|
657
|
-
* Replayed from the turn's persisted `
|
|
663
|
+
* Replayed from the turn's persisted `ATTACHMENTS` row, so a reloaded
|
|
658
664
|
* answer carries the citations the live one did. */
|
|
659
665
|
sources?: ChatSource[];
|
|
660
666
|
/** Entity references this answer's metadata described — see `Message.refs`. */
|
|
@@ -239,7 +239,7 @@ export interface UnifiedChatMessage {
|
|
|
239
239
|
/**
|
|
240
240
|
* Documents this answer cited, rendered as chips beneath it. Produced by
|
|
241
241
|
* BOTH transports now — SSE reads them off the per-turn metadata frame, NATS
|
|
242
|
-
* decodes them out of
|
|
242
|
+
* decodes them out of an `ATTACHMENTS` chunk.
|
|
243
243
|
*/
|
|
244
244
|
sources?: ChatSource[];
|
|
245
245
|
/**
|
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
// One normalizer for ask rows, shared with the live decoder — history and the
|
|
35
35
|
// stream must agree on which options are usable.
|
|
36
36
|
import { normalizeAskOptions } from '../../../chat-protocol/nats-decoder';
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
attachmentsEvent,
|
|
39
|
+
mergeSourceMetadata,
|
|
40
|
+
sourceMetadataEvent,
|
|
41
|
+
type SourceMetadata,
|
|
42
|
+
} from '../../../chat-protocol/source-metadata';
|
|
38
43
|
import { applyApprovalStatusToSegment } from '../stream/message-mutations';
|
|
39
44
|
import {
|
|
40
45
|
MESSAGE_TYPE,
|
|
@@ -122,11 +127,14 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
|
|
|
122
127
|
}
|
|
123
128
|
return null;
|
|
124
129
|
|
|
125
|
-
// Persisted
|
|
126
|
-
// (`
|
|
130
|
+
// Persisted attachments, through the SAME decoder the live chunk uses
|
|
131
|
+
// (`attachmentsEvent`). That shared parse is the whole reason a reloaded
|
|
127
132
|
// answer renders identically to the one the reader watched arrive.
|
|
133
|
+
case MESSAGE_TYPE.ATTACHMENTS:
|
|
134
|
+
return attachmentsEvent(data);
|
|
135
|
+
|
|
136
|
+
// The envelope `ATTACHMENTS` replaced (see `MESSAGE_TYPE.GUIDE`).
|
|
128
137
|
case MESSAGE_TYPE.GUIDE:
|
|
129
|
-
case MESSAGE_TYPE.SOURCES:
|
|
130
138
|
return 'payload' in data ? sourceMetadataEvent(data.payload) : null;
|
|
131
139
|
|
|
132
140
|
// Same completeness gate as the live decoder (`decodeNatsChunk`): a
|
|
@@ -34,6 +34,8 @@ export type {
|
|
|
34
34
|
KeepRecordingSelection,
|
|
35
35
|
ReleaseKeepingModalProps,
|
|
36
36
|
} from './remote-session-keep-modals';
|
|
37
|
+
export { AssignTicketModal, UnassignTicketModal } from './remote-session-ticket-modals';
|
|
38
|
+
export type { AssignTicketModalProps, UnassignTicketModalProps } from './remote-session-ticket-modals';
|
|
37
39
|
export {
|
|
38
40
|
RemoteSessionEventList,
|
|
39
41
|
RemoteSessionTimelineMarkers,
|
|
@@ -151,8 +151,8 @@ export interface ReleaseKeepingModalProps {
|
|
|
151
151
|
keptOn: string;
|
|
152
152
|
/** The reason as it reads in the sentence, e.g. "Client dispute". */
|
|
153
153
|
reason: string;
|
|
154
|
-
/** The
|
|
155
|
-
|
|
154
|
+
/** The tickets assigned to the recording, as they read in the line, e.g. "TKT-4631"; the line is left out without any. */
|
|
155
|
+
tickets?: string[];
|
|
156
156
|
/** When the recording will be deleted once released, formatted. */
|
|
157
157
|
expiresOn: string;
|
|
158
158
|
/** The original expiry date when it has already passed, so the release adds the grace period. */
|
|
@@ -169,7 +169,7 @@ export function ReleaseKeepingModal({
|
|
|
169
169
|
keptBy,
|
|
170
170
|
keptOn,
|
|
171
171
|
reason,
|
|
172
|
-
|
|
172
|
+
tickets = [],
|
|
173
173
|
expiresOn,
|
|
174
174
|
dueOn,
|
|
175
175
|
isPending = false,
|
|
@@ -185,7 +185,7 @@ export function ReleaseKeepingModal({
|
|
|
185
185
|
<p>
|
|
186
186
|
Kept by {keptBy} on {keptOn} for {reason}.
|
|
187
187
|
</p>
|
|
188
|
-
{
|
|
188
|
+
{tickets.length > 0 && <p>Ticket: {tickets.join(', ')}</p>}
|
|
189
189
|
</div>
|
|
190
190
|
|
|
191
191
|
<KeepNotice icon={<HourglassIcon size={24} color="currentColor" />} title={`It will expire on ${expiresOn}`}>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Autocomplete, type AutocompleteOption } from '../../ui/autocomplete';
|
|
5
|
+
import { Button } from '../../ui/button/button';
|
|
6
|
+
import { ModalV2, ModalV2Footer, ModalV2Header, ModalV2Title } from '../../ui/modal-v2';
|
|
7
|
+
|
|
8
|
+
export interface AssignTicketModalProps {
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* The tickets that can be assigned: label = title, value = ticket id. Leave
|
|
13
|
+
* out the ones already assigned to the recording.
|
|
14
|
+
*/
|
|
15
|
+
options: AutocompleteOption<string>[];
|
|
16
|
+
/** Server-side search: called with the typed text; the caller refreshes `options`. Client-side filtering otherwise. */
|
|
17
|
+
onSearch?: (query: string) => void;
|
|
18
|
+
/** The ticket search is in flight. */
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
/** The assignment is in flight: the button spins and the dialog refuses to close. */
|
|
21
|
+
isPending?: boolean;
|
|
22
|
+
onConfirm: (ticketIds: string[]) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Assign Ticket: links one or more tickets to a recording. */
|
|
26
|
+
export function AssignTicketModal({
|
|
27
|
+
isOpen,
|
|
28
|
+
onClose,
|
|
29
|
+
options,
|
|
30
|
+
onSearch,
|
|
31
|
+
loading = false,
|
|
32
|
+
isPending = false,
|
|
33
|
+
onConfirm,
|
|
34
|
+
}: AssignTicketModalProps) {
|
|
35
|
+
const [ticketIds, setTicketIds] = useState<string[]>([]);
|
|
36
|
+
// Titles of the picked tickets: a server-side search replaces `options`, and
|
|
37
|
+
// a picked ticket missing from the new results would otherwise show its id.
|
|
38
|
+
const [titles, setTitles] = useState<Record<string, string>>({});
|
|
39
|
+
|
|
40
|
+
// Every opening starts blank. Reset while rendering, not from an effect: the
|
|
41
|
+
// modal stays mounted when it closes, so an effect would paint the opening
|
|
42
|
+
// frame with the previous selection still in the field.
|
|
43
|
+
const [seededOpen, setSeededOpen] = useState(isOpen);
|
|
44
|
+
if (seededOpen !== isOpen) {
|
|
45
|
+
setSeededOpen(isOpen);
|
|
46
|
+
if (isOpen) {
|
|
47
|
+
setTicketIds([]);
|
|
48
|
+
setTitles({});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleChange = (ids: string[]) => {
|
|
53
|
+
setTicketIds(ids);
|
|
54
|
+
setTitles(prev => {
|
|
55
|
+
const next: Record<string, string> = {};
|
|
56
|
+
for (const id of ids) next[id] = options.find(option => option.value === id)?.label ?? prev[id] ?? id;
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleAssign = () => {
|
|
62
|
+
if (ticketIds.length === 0 || isPending) return;
|
|
63
|
+
onConfirm(ticketIds);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ModalV2 isOpen={isOpen} onClose={isPending ? () => {} : onClose} className="text-left md:max-w-[600px]">
|
|
68
|
+
<ModalV2Header>
|
|
69
|
+
<ModalV2Title>Assign Ticket</ModalV2Title>
|
|
70
|
+
</ModalV2Header>
|
|
71
|
+
|
|
72
|
+
<Autocomplete
|
|
73
|
+
multiple
|
|
74
|
+
wrapTags
|
|
75
|
+
label="Tickets"
|
|
76
|
+
labelVariant="large"
|
|
77
|
+
options={options}
|
|
78
|
+
value={ticketIds}
|
|
79
|
+
onChange={handleChange}
|
|
80
|
+
renderTag={option => titles[option.value] ?? option.label}
|
|
81
|
+
onInputChange={onSearch ? (value, reason) => reason === 'input' && onSearch(value) : undefined}
|
|
82
|
+
disableClientFilter={Boolean(onSearch)}
|
|
83
|
+
loading={loading}
|
|
84
|
+
noOptionsText="No tickets found"
|
|
85
|
+
disabled={isPending}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<ModalV2Footer>
|
|
89
|
+
<Button type="button" variant="outline" onClick={onClose} disabled={isPending} className="flex-1 md:hidden">
|
|
90
|
+
Cancel
|
|
91
|
+
</Button>
|
|
92
|
+
<div className="hidden flex-1 md:block" />
|
|
93
|
+
<Button
|
|
94
|
+
type="button"
|
|
95
|
+
variant="accent"
|
|
96
|
+
onClick={handleAssign}
|
|
97
|
+
loading={isPending}
|
|
98
|
+
disabled={ticketIds.length === 0}
|
|
99
|
+
className="flex-1"
|
|
100
|
+
>
|
|
101
|
+
Assign
|
|
102
|
+
</Button>
|
|
103
|
+
</ModalV2Footer>
|
|
104
|
+
</ModalV2>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface UnassignTicketModalProps {
|
|
109
|
+
isOpen: boolean;
|
|
110
|
+
onClose: () => void;
|
|
111
|
+
/** The unassignment is in flight: the button spins and the dialog refuses to close. */
|
|
112
|
+
isPending?: boolean;
|
|
113
|
+
onConfirm: () => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Unassign Ticket: unlinks a ticket from a recording; the ticket itself is unchanged. */
|
|
117
|
+
export function UnassignTicketModal({ isOpen, onClose, isPending = false, onConfirm }: UnassignTicketModalProps) {
|
|
118
|
+
return (
|
|
119
|
+
<ModalV2 isOpen={isOpen} onClose={isPending ? () => {} : onClose} className="text-left md:max-w-[600px]">
|
|
120
|
+
<ModalV2Header>
|
|
121
|
+
<ModalV2Title>Unassign Ticket</ModalV2Title>
|
|
122
|
+
</ModalV2Header>
|
|
123
|
+
|
|
124
|
+
<p className="w-full text-ods-text-primary text-h4">
|
|
125
|
+
This Ticket will no longer be linked to this remote session. The ticket itself stays as it is, and you can
|
|
126
|
+
assign it again anytime.
|
|
127
|
+
</p>
|
|
128
|
+
|
|
129
|
+
<ModalV2Footer>
|
|
130
|
+
<Button type="button" variant="outline" onClick={onClose} disabled={isPending} className="flex-1 md:hidden">
|
|
131
|
+
Cancel
|
|
132
|
+
</Button>
|
|
133
|
+
<div className="hidden flex-1 md:block" />
|
|
134
|
+
<Button type="button" variant="destructive" onClick={onConfirm} loading={isPending} className="flex-1">
|
|
135
|
+
Unassign
|
|
136
|
+
</Button>
|
|
137
|
+
</ModalV2Footer>
|
|
138
|
+
</ModalV2>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -140,6 +140,12 @@ export interface AutocompleteMultipleProps<T = string> extends AutocompleteBaseP
|
|
|
140
140
|
limitTags?: number | 'auto';
|
|
141
141
|
/** Custom render function for the "+N" overflow chip */
|
|
142
142
|
getLimitTagsText?: (more: number) => ReactNode;
|
|
143
|
+
/**
|
|
144
|
+
* Show every tag in full, wrapping onto more lines: the field grows instead
|
|
145
|
+
* of truncating labels and collapsing the rest into "+N". `limitTags` is
|
|
146
|
+
* ignored. For short pickers whose picks must stay readable (ticket titles).
|
|
147
|
+
*/
|
|
148
|
+
wrapTags?: boolean;
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
export type AutocompleteProps<T = string> = AutocompleteSingleProps<T> | AutocompleteMultipleProps<T>;
|
|
@@ -189,6 +195,7 @@ function AutocompleteInner<T = string>(props: AutocompleteProps<T>, ref: Forward
|
|
|
189
195
|
// Multiple-only props
|
|
190
196
|
const maxItems = multiple ? (props as AutocompleteMultipleProps<T>).maxItems : undefined;
|
|
191
197
|
const renderTag = multiple ? (props as AutocompleteMultipleProps<T>).renderTag : undefined;
|
|
198
|
+
const wrapTags = multiple && ((props as AutocompleteMultipleProps<T>).wrapTags ?? false);
|
|
192
199
|
const limitTagsProp = multiple ? ((props as AutocompleteMultipleProps<T>).limitTags ?? 'auto') : 'auto';
|
|
193
200
|
const getLimitTagsText = multiple
|
|
194
201
|
? ((props as AutocompleteMultipleProps<T>).getLimitTagsText ?? ((more: number) => `+${more}`))
|
|
@@ -252,7 +259,7 @@ function AutocompleteInner<T = string>(props: AutocompleteProps<T>, ref: Forward
|
|
|
252
259
|
inputRef,
|
|
253
260
|
} = useAutoLimitTags({
|
|
254
261
|
count: multiple ? selectedOptions.length : 0,
|
|
255
|
-
limitTags: multiple ? limitTagsProp : 0,
|
|
262
|
+
limitTags: multiple ? (wrapTags ? selectedOptions.length : limitTagsProp) : 0,
|
|
256
263
|
placeholder: inputPlaceholder,
|
|
257
264
|
});
|
|
258
265
|
|
|
@@ -506,15 +513,22 @@ function AutocompleteInner<T = string>(props: AutocompleteProps<T>, ref: Forward
|
|
|
506
513
|
</span>
|
|
507
514
|
)}
|
|
508
515
|
|
|
509
|
-
{/* Middle zone: tags + input — single line with overflow */}
|
|
510
|
-
<div
|
|
516
|
+
{/* Middle zone: tags + input — single line with overflow, or wrapped lines with `wrapTags` */}
|
|
517
|
+
<div
|
|
518
|
+
ref={middleRef}
|
|
519
|
+
className={cn(
|
|
520
|
+
'flex min-w-0 flex-1 items-center gap-2 px-2',
|
|
521
|
+
wrapTags ? 'flex-wrap py-[var(--spacing-system-xsf)]' : 'overflow-hidden',
|
|
522
|
+
)}
|
|
523
|
+
>
|
|
511
524
|
{/* Tags (multiple mode only) */}
|
|
512
525
|
{multiple &&
|
|
513
526
|
visibleTags.map(option => (
|
|
514
527
|
<Tag
|
|
515
528
|
key={String(option.value)}
|
|
516
529
|
variant="outline"
|
|
517
|
-
|
|
530
|
+
className={wrapTags ? 'max-w-full' : undefined}
|
|
531
|
+
labelClassName={wrapTags ? 'truncate' : 'max-w-[90px] truncate'}
|
|
518
532
|
label={renderTag ? renderTag(option) : option.label}
|
|
519
533
|
onClose={
|
|
520
534
|
!disabled
|