diff --git a/frontend/src/lib/components/ChannelSelector.svelte b/frontend/src/lib/components/ChannelSelector.svelte index fe5ee8f5c5..b6e77e06d9 100644 --- a/frontend/src/lib/components/ChannelSelector.svelte +++ b/frontend/src/lib/components/ChannelSelector.svelte @@ -18,6 +18,7 @@ channels?: ChannelItem[] teamId?: string onError?: (error: Error) => void + onselectedchannelchange?: (channel: ChannelItem | undefined) => void } let { @@ -28,34 +29,56 @@ minWidth = '160px', channels = undefined, teamId, - onError + onError, + onselectedchannelchange }: Props = $props() let isFetching = $state(false) let searchResults = $state([]) - // Only enable search mode if no channels are provided AND teamId is provided + let selectedChannelId = $state(selectedChannel?.channel_id) + const searchMode = !channels && !!teamId - // Determine which channels to show: provided channels or search results - // In search mode, include the selected channel if it exists - let displayChannels = $derived(() => { - const baseChannels = channels || searchResults; - if (searchMode && selectedChannel && !baseChannels.find(c => c.channel_id === selectedChannel?.channel_id)) { - return [selectedChannel, ...baseChannels]; + let displayChannels = $derived.by(() => { + const baseChannels = channels || searchResults + if (selectedChannel && !baseChannels.find(c => c.channel_id === selectedChannel?.channel_id)) { + return [selectedChannel, ...baseChannels] + } + return baseChannels + }) + + $effect(() => { + const newChannel = selectedChannelId + ? displayChannels.find(c => c.channel_id === selectedChannelId) + : undefined + + if (newChannel?.channel_id !== selectedChannel?.channel_id) { + selectedChannel = newChannel + } + }) + + $effect(() => { + if (selectedChannel?.channel_id !== selectedChannelId) { + selectedChannelId = selectedChannel?.channel_id + } + }) + + let previousChannelId = $state(undefined) + + $effect(() => { + if (selectedChannel?.channel_id !== previousChannelId) { + previousChannelId = selectedChannel?.channel_id + onselectedchannelchange?.(selectedChannel) } - return baseChannels; }) - // Create separate filter text for search mode let searchFilterText = $state('') - // Debounced search function const debouncedSearch = debounce(async (query: string) => { await searchChannels(query) }, 500) - // Watch for search filter text changes (only in search mode) $effect(() => { if (searchMode) { if (searchFilterText.length >= 1) { @@ -97,37 +120,27 @@ {#if searchMode} channel.channel_id && channel.channel_name).map((channel) => ({ + items={displayChannels.filter(channel => channel.channel_id && channel.channel_name).map((channel) => ({ label: channel.channel_name ?? 'Unknown Channel', value: channel.channel_id ?? '' }))} {placeholder} clearable - disabled={disabled || displayChannels().length === 0} - bind:value={ - () => selectedChannel?.channel_id, - (value) => { - selectedChannel = value ? displayChannels().find((channel) => channel.channel_id === value) : undefined - } - } + disabled={disabled || displayChannels.length === 0} + bind:value={selectedChannelId} /> {/if} diff --git a/frontend/src/lib/components/ConnectionSection.svelte b/frontend/src/lib/components/ConnectionSection.svelte index 663b6c1114..63dce1727f 100644 --- a/frontend/src/lib/components/ConnectionSection.svelte +++ b/frontend/src/lib/components/ConnectionSection.svelte @@ -49,7 +49,7 @@ hideConnectButton?: boolean } = $props() - let selectedTeam: TeamItem | undefined = undefined + let selectedTeam: TeamItem | undefined = $state(undefined) async function connectTeams() { if (!selectedTeam) return diff --git a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte index 9433e169f0..25bc2f9adf 100644 --- a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte +++ b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte @@ -496,19 +496,16 @@ minWidth="200px" placeholder="Search Teams channels" teamId={teams_team_id} - bind:selectedChannel={ - () => - handlerExtraArgs['channel'] - ? { - channel_id: handlerExtraArgs['channel'], - channel_name: handlerExtraArgs['channel_name'] - } - : undefined, - (channel) => { - handlerExtraArgs['channel'] = channel?.channel_id - handlerExtraArgs['channel_name'] = channel?.channel_name - } - } + selectedChannel={handlerExtraArgs['channel'] + ? { + channel_id: handlerExtraArgs['channel'], + channel_name: handlerExtraArgs['channel_name'] + } + : undefined} + onselectedchannelchange={(channel) => { + handlerExtraArgs['channel'] = channel?.channel_id + handlerExtraArgs['channel_name'] = channel?.channel_name + }} onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)} /> diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 81e34ff51c..92685107a9 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -163,13 +163,17 @@ teamItem: { team_id: string; team_name: string } | undefined, i: number ) { + const currentTeamChannel = $values['critical_error_channels'][i]?.teams_channel + const teamIdChanged = currentTeamChannel?.team_id !== teamItem?.team_id + $values['critical_error_channels'][i] = { teams_channel: teamItem ? { team_id: teamItem.team_id, team_name: teamItem.team_name, - channel_id: undefined, // Will be set when channel is selected - channel_name: undefined + // Preserve existing channel if team didn't actually change + channel_id: teamIdChanged ? undefined : currentTeamChannel?.channel_id, + channel_name: teamIdChanged ? undefined : currentTeamChannel?.channel_name } : undefined } @@ -191,6 +195,7 @@ } } } + @@ -563,24 +568,25 @@ value={v?.slack_channel ?? ''} /> {:else if v && 'teams_channel' in v} + {@const currentTeam = $values['critical_error_channels'][i]?.teams_channel + ? { + team_id: $values['critical_error_channels'][i]?.teams_channel?.team_id, + team_name: $values['critical_error_channels'][i]?.teams_channel?.team_name + } + : undefined} + {@const currentChannel = $values['critical_error_channels'][i]?.teams_channel?.channel_id + ? { + channel_id: $values['critical_error_channels'][i]?.teams_channel?.channel_id, + channel_name: $values['critical_error_channels'][i]?.teams_channel?.channel_name + } + : undefined}
- $values['critical_error_channels'][i]?.teams_channel - ? { - team_id: - $values['critical_error_channels'][i]?.teams_channel?.team_id, - team_name: - $values['critical_error_channels'][i]?.teams_channel - ?.team_name - } - : undefined, - (team) => handleTeamChange(team, i) - } + selectedTeam={currentTeam} + onselectedteamchange={(team) => handleTeamChange(team, i)} /> {#if $values['critical_error_channels'][i]?.teams_channel?.team_id} @@ -588,20 +594,8 @@ containerClass="" placeholder="Search channels" teamId={$values['critical_error_channels'][i]?.teams_channel?.team_id} - bind:selectedChannel={ - () => - $values['critical_error_channels'][i]?.teams_channel?.channel_id - ? { - channel_id: - $values['critical_error_channels'][i]?.teams_channel - ?.channel_id, - channel_name: - $values['critical_error_channels'][i]?.teams_channel - ?.channel_name - } - : undefined, - (channel) => handleChannelChange(channel, i) - } + selectedChannel={currentChannel} + onselectedchannelchange={(channel) => handleChannelChange(channel, i)} onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)} /> diff --git a/frontend/src/lib/components/TeamSelector.svelte b/frontend/src/lib/components/TeamSelector.svelte index d8d0fbc2e9..e55f0bec8f 100644 --- a/frontend/src/lib/components/TeamSelector.svelte +++ b/frontend/src/lib/components/TeamSelector.svelte @@ -18,6 +18,7 @@ teams?: TeamItem[] | undefined minWidth?: string onError?: (error: Error) => void + onselectedteamchange?: (team: TeamItem | undefined) => void } let { @@ -27,34 +28,56 @@ showRefreshButton = true, teams = undefined, minWidth = '160px', - onError + onError, + onselectedteamchange }: Props = $props() let isFetching = $state(false) let searchResults = $state([]) - // Only enable search mode if no teams are provided + let selectedTeamId = $state(selectedTeam?.team_id) + const searchMode = !teams - // Determine which teams to show: provided teams or search results - // In search mode, include the selected team if it exists - let displayTeams = $derived(() => { - const baseTeams = teams || searchResults; - if (searchMode && selectedTeam && !baseTeams.find(t => t.team_id === selectedTeam?.team_id)) { - return [selectedTeam, ...baseTeams]; + let displayTeams = $derived.by(() => { + const baseTeams = teams || searchResults + if (selectedTeam && !baseTeams.find(t => t.team_id === selectedTeam?.team_id)) { + return [selectedTeam, ...baseTeams] + } + return baseTeams + }) + + $effect(() => { + const newTeam = selectedTeamId + ? displayTeams.find(t => t.team_id === selectedTeamId) + : undefined + + if (newTeam?.team_id !== selectedTeam?.team_id) { + selectedTeam = newTeam + } + }) + + $effect(() => { + if (selectedTeam?.team_id !== selectedTeamId) { + selectedTeamId = selectedTeam?.team_id + } + }) + + let previousTeamId = $state(undefined) + + $effect(() => { + if (selectedTeam?.team_id !== previousTeamId) { + previousTeamId = selectedTeam?.team_id + onselectedteamchange?.(selectedTeam) } - return baseTeams; }) - // Create separate filter text for search mode let searchFilterText = $state('') - // Debounced search function const debouncedSearch = debounce(async (query: string) => { await searchTeams(query) }, 500) - // Watch for search filter text changes (only in search mode) $effect(() => { if (searchMode) { if (searchFilterText.length >= 1) { @@ -100,37 +123,27 @@ {#if searchMode} ({ + items={displayTeams.map((team) => ({ label: team.team_name, value: team.team_id }))} placeholder="Select a team" clearable disabled={disabled || isFetching} - bind:value={ - () => selectedTeam?.team_id, - (value) => { - selectedTeam = value ? displayTeams().find((team) => team.team_id === value) : undefined - } - } + bind:value={selectedTeamId} /> {/if}