feat(admin-tickets): deep-link to a specific ticket from notifications

Add an /admin/tickets/:ticketId route that pre-selects the ticket, and a
StartParamNavigator that maps a Telegram Mini App start param (admin_ticket_<id>,
delivered by a group-chat t.me/<bot>/<app>?startapp deep link) to that route.

This lets the bot's admin ticket notification buttons open the cabinet straight on
the ticket — web_app buttons for private chats, the startapp deep link for groups
where web_app is unavailable. Selection mirrors the URL param (cleared on the bare
/admin/tickets list). The static /admin/tickets/settings route still out-ranks the
new dynamic segment.

Pairs with bot feature BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot#2988.
This commit is contained in:
Fringg
2026-06-19 12:57:27 +03:00
parent ecc2d45562
commit ff0b119ebc
3 changed files with 73 additions and 1 deletions

View File

@@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react';
import logger from '../utils/logger';
import { linkifyText } from '../utils/linkify';
import { MessageMediaGrid } from '../components/tickets/MessageMediaGrid';
import { useNavigate } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { adminApi, AdminTicket, AdminTicketDetail } from '../api/admin';
@@ -53,10 +53,29 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
export default function AdminTickets() {
const { t } = useTranslation();
const navigate = useNavigate();
const { ticketId } = useParams<{ ticketId: string }>();
const queryClient = useQueryClient();
const { capabilities } = usePlatform();
const [selectedTicketId, setSelectedTicketId] = useState<number | null>(null);
// Deep-link: /admin/tickets/:ticketId (or a startapp param routed here) opens
// the given ticket directly — used by the admin-chat notification buttons.
// Both routes render the same component instance (no remount), so we mirror the
// URL param into the selection: navigating to the bare /admin/tickets list
// clears any deep-linked selection, keeping URL and detail pane in sync. (This
// only fires on mount or an actual param change, never on in-list clicks, since
// ticketId stays undefined on the bare route.)
useEffect(() => {
if (!ticketId) {
setSelectedTicketId(null);
return;
}
const id = Number(ticketId);
if (Number.isInteger(id) && id > 0) {
setSelectedTicketId(id);
}
}, [ticketId]);
const [statusFilter, setStatusFilter] = useState<string>('');
const [replyText, setReplyText] = useState('');
const [isReplying, setIsReplying] = useState(false);