/** * Copyright 2025 LY Corporation * * LY Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ import { useEffect, useMemo, useState } from 'react'; import type { GetServerSideProps } from 'next'; import SupportOperationsShell from '@/features/support-portal/ui/support-operations-shell.ui'; import SupportStatusBadge from '@/features/support-portal/ui/support-status-badge.ui'; import type { NextPageWithLayout } from '@/shared/types'; import type { SupportTicketRecord } from '@/server/support-stub'; import serverSideTranslations from '@/server-side-translations'; import { AnonymousLayout } from '@/widgets/anonymous-layout'; const OpsPage: NextPageWithLayout = () => { const [tickets, setTickets] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(''); const [workingId, setWorkingId] = useState(null); const loadTickets = async () => { setIsLoading(true); setErrorMessage(''); try { const response = await fetch('/api/support/tickets'); const data = (await response.json()) as SupportTicketRecord[] | { message?: string }; if (!response.ok) { throw new Error('message' in data && data.message ? data.message : '승인 큐를 불러오지 못했습니다.'); } setTickets(data as SupportTicketRecord[]); } catch (error) { setErrorMessage(error instanceof Error ? error.message : '승인 큐 조회 중 오류가 발생했습니다.'); } finally { setIsLoading(false); } }; useEffect(() => { void loadTickets(); }, []); const pendingTickets = useMemo( () => tickets.filter((ticket) => ticket.requires_approval && ticket.approval_status === 'PENDING'), [tickets], ); const approvedTickets = useMemo( () => tickets.filter((ticket) => ticket.approval_status === 'APPROVED'), [tickets], ); const handleApprove = async (ticketId: number) => { setWorkingId(ticketId); try { const response = await fetch(`/api/support/tickets/${ticketId}/approve`, { method: 'POST', }); const data = (await response.json()) as { message?: string }; if (!response.ok) { throw new Error(data.message ?? '승인 처리에 실패했습니다.'); } await loadTickets(); } catch (error) { setErrorMessage(error instanceof Error ? error.message : '승인 처리 중 오류가 발생했습니다.'); } finally { setWorkingId(null); } }; return (

Queue state

pending={pendingTickets.length}
approved={approvedTickets.length}

} >

Approval queue

승인 대기 요청

{isLoading &&

승인 큐를 불러오는 중입니다...

} {!isLoading && errorMessage && (
{errorMessage}
)} {!isLoading && !errorMessage && pendingTickets.length === 0 && (
현재 승인 대기 중인 요청이 없습니다.
)}
{pendingTickets.map((ticket) => (
{ticket.workspace_name} • Ticket {ticket.ticket_id}

{ticket.title}

{ticket.description}

requester {ticket.requester_id}

승인 시 운영 큐 상태가 정리되고, 관리자 페이지에서 이슈 생성이 가능해집니다.

))}
); }; OpsPage.getLayout = (page: React.ReactNode) => { return {page}; }; export const getServerSideProps: GetServerSideProps = async ({ locale }) => { return { props: { ...(await serverSideTranslations(locale)), }, }; }; export default OpsPage;