diff --git a/apps/admin/src/app/regions-countries/page.tsx b/apps/admin/src/app/regions-countries/page.tsx new file mode 100644 index 00000000..d4cbe384 --- /dev/null +++ b/apps/admin/src/app/regions-countries/page.tsx @@ -0,0 +1,10 @@ +import { RequireAdminSession } from "@/components/features/auth/RequireAdminSession"; +import { RegionsCountriesPageContent } from "@/components/features/regions-countries/RegionsCountriesPageContent"; + +export default function RegionsCountriesPage() { + return ( + + + + ); +} diff --git a/apps/admin/src/components/features/mentor-applications/MentorApplicationsPageContent.tsx b/apps/admin/src/components/features/mentor-applications/MentorApplicationsPageContent.tsx index 58155fb5..c9615163 100644 --- a/apps/admin/src/components/features/mentor-applications/MentorApplicationsPageContent.tsx +++ b/apps/admin/src/components/features/mentor-applications/MentorApplicationsPageContent.tsx @@ -123,6 +123,12 @@ const getUniversityName = (application: MentorApplicationListItem) => { return pickString(core.universityName, university?.koreanName, university?.name, core.universityId, university?.id); }; +const getUniversityId = (application: MentorApplicationListItem) => { + const core = getApplicationCore(application); + const university = getUniversity(application); + return pickString(core.universityId, university?.universityId, university?.id); +}; + const getTerm = (application: MentorApplicationListItem) => { const core = getApplicationCore(application); return pickString(core.term); @@ -177,6 +183,46 @@ const getHistoryItems = (data: MentorApplicationHistoryResponse | undefined) => return Array.isArray(data) ? data : (data.content ?? []); }; +type MentorApplicationCountData = Awaited>; + +const toCountNumber = (value: unknown): number | undefined => { + if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "string" && value.trim().length > 0) { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : undefined; + } + if (typeof value === "object" && value !== null) { + const record = value as Record; + return toCountNumber(record.count ?? record.total ?? record.value); + } + + return undefined; +}; + +const getCountByStatus = (data: MentorApplicationCountData | undefined, status: MentorApplicationStatus) => { + if (!data) return undefined; + + if (Array.isArray(data)) { + const item = data.find( + (entry) => normalizeStatus(entry.mentorApplicationStatus ?? entry.status ?? entry.name) === status, + ); + return toCountNumber(item); + } + + const record = data as Record; + const collection = record.content ?? record.data ?? record.items ?? record.result; + if (Array.isArray(collection)) { + const item = collection.find((entry) => { + if (typeof entry !== "object" || entry === null) return false; + const nextRecord = entry as Record; + return normalizeStatus(nextRecord.mentorApplicationStatus ?? nextRecord.status ?? nextRecord.name) === status; + }); + return toCountNumber(item); + } + + return toCountNumber(record[status] ?? record[status.toLowerCase()]); +}; + function MentorApplicationStatusBadge({ status }: { status: MentorApplicationStatus | null }) { if (!status) { return -; @@ -265,6 +311,8 @@ export function MentorApplicationsPageContent() { const [expandedSiteUserId, setExpandedSiteUserId] = useState(null); const [rejectingApplicationId, setRejectingApplicationId] = useState(null); const [rejectReason, setRejectReason] = useState(""); + const [mappingApplicationId, setMappingApplicationId] = useState(null); + const [mappingUniversityId, setMappingUniversityId] = useState(""); const normalizedNickname = nickname.trim(); @@ -281,12 +329,24 @@ export function MentorApplicationsPageContent() { placeholderData: keepPreviousData, }); + const countQuery = useQuery({ + queryKey: ["mentorApplications", "count"], + queryFn: adminApi.getCountMentorApplicationByStatus, + placeholderData: keepPreviousData, + }); + useEffect(() => { if (isError) { toast.error("멘토 승격 요청 목록을 불러오지 못했습니다."); } }, [isError]); + useEffect(() => { + if (countQuery.isError) { + toast.error("멘토 승격 요청 상태별 개수를 불러오지 못했습니다."); + } + }, [countQuery.isError]); + const applications = data?.content ?? []; const totalPages = Math.max(1, data?.totalPages ?? 1); const totalElements = data?.totalElements; @@ -316,6 +376,20 @@ export function MentorApplicationsPageContent() { }, }); + const mapUniversityMutation = useMutation({ + mutationFn: ({ mentorApplicationId, universityId }: { mentorApplicationId: string; universityId: number }) => + adminApi.assignMentorApplicationUniversity(mentorApplicationId, universityId), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["mentorApplications"] }); + setMappingApplicationId(null); + setMappingUniversityId(""); + toast.success("멘토 지원서 대학을 매핑했습니다."); + }, + onError: () => { + toast.error("멘토 지원서 대학 매핑에 실패했습니다."); + }, + }); + const handlePageChange = (newPage: number) => { if (newPage < 1 || newPage > totalPages) return; setPage(newPage); @@ -372,12 +446,61 @@ export function MentorApplicationsPageContent() { await rejectMutation.mutateAsync({ mentorApplicationId, rejectedReason: normalizedReason }); }; + const handleStartMapUniversity = (application: MentorApplicationListItem) => { + const applicationId = getApplicationId(application); + if (!applicationId) { + toast.error("신청 ID를 확인할 수 없습니다."); + return; + } + + setMappingApplicationId(applicationId); + setMappingUniversityId(getUniversityId(application) ?? ""); + }; + + const handleCancelMapUniversity = () => { + setMappingApplicationId(null); + setMappingUniversityId(""); + }; + + const handleMapUniversity = async (mentorApplicationId: string) => { + const normalizedUniversityId = Number(mappingUniversityId.trim()); + if (!Number.isInteger(normalizedUniversityId) || normalizedUniversityId <= 0) { + toast.error("대학 ID를 숫자로 입력해주세요."); + return; + } + + await mapUniversityMutation.mutateAsync({ mentorApplicationId, universityId: normalizedUniversityId }); + }; + return ( + + {STATUS_OPTIONS.map((option) => { + const count = getCountByStatus(countQuery.data, option.value); + const isActive = statusFilter === option.value; + + return ( + handleFilterStatus(option.value)} + className={`rounded-xl border px-4 py-3 text-left transition-colors ${ + isActive ? "border-primary bg-primary-100" : "border-k-100 bg-k-0 hover:bg-k-50" + }`} + > + {option.label} + + {countQuery.isLoading ? "..." : typeof count === "number" ? count.toLocaleString() : "-"} + + + ); + })} + + 상태 @@ -489,9 +612,12 @@ export function MentorApplicationsPageContent() { const status = getApplicationStatus(application); const fileUrl = getVerificationFileUrl(application); const profileImageUrl = getProfileImageUrl(application); + const universityId = getUniversityId(application); const isRejecting = Boolean(applicationId && rejectingApplicationId === applicationId); + const isMappingUniversity = Boolean(applicationId && mappingApplicationId === applicationId); const isExpanded = Boolean(siteUserId && expandedSiteUserId === siteUserId); - const isActionPending = approveMutation.isPending || rejectMutation.isPending; + const isActionPending = + approveMutation.isPending || rejectMutation.isPending || mapUniversityMutation.isPending; return ( @@ -525,6 +651,7 @@ export function MentorApplicationsPageContent() { {toDisplayText(getCountry(application))} / {toDisplayText(getTerm(application))} + 대학 ID {toDisplayText(universityId)} {formatDateTime(getCreatedAt(application))} @@ -560,7 +687,28 @@ export function MentorApplicationsPageContent() { {status === "PENDING" && applicationId ? ( - isRejecting ? ( + isMappingUniversity ? ( + + setMappingUniversityId(event.target.value)} + placeholder="대학 ID" + className="h-8 w-[120px] rounded-md border border-k-200 bg-k-0 px-2 typo-regular-4 text-k-700 outline-none placeholder:text-k-400 focus-visible:border-primary" + /> + handleMapUniversity(applicationId)} + > + 확인 + + + 취소 + + + ) : isRejecting ? ( ) : ( + handleStartMapUniversity(application)} + > + 대학 매핑 + { + if (value === null || value === undefined) return undefined; + const normalized = String(value).trim(); + return normalized.length > 0 ? normalized : undefined; +}; + +const toDisplayText = (value: string | number | null | undefined) => toOptionalString(value) ?? "-"; + +const normalizeCollection = (data: AdminCollection | undefined) => { + if (!data) return []; + if (Array.isArray(data)) return data; + return data.content ?? data.data ?? data.items ?? data.result ?? []; +}; + +const getRegionCode = (region: RegionResponse) => toOptionalString(region.code); +const getRegionName = (region: RegionResponse) => toOptionalString(region.koreanName ?? region.name); +const getCountryCode = (country: CountryResponse) => toOptionalString(country.code); +const getCountryName = (country: CountryResponse) => toOptionalString(country.koreanName ?? country.name); + +const getCountryRegionCode = (country: CountryResponse) => { + if (typeof country.region === "string") { + return toOptionalString(country.region); + } + + return toOptionalString(country.regionCode ?? country.region?.code ?? country.region?.regionCode); +}; + +export function RegionsCountriesPageContent() { + const queryClient = useQueryClient(); + const [regionCode, setRegionCode] = useState(""); + const [regionName, setRegionName] = useState(""); + const [countryCode, setCountryCode] = useState(""); + const [countryName, setCountryName] = useState(""); + const [countryRegionCode, setCountryRegionCode] = useState(""); + const [editingRegionCode, setEditingRegionCode] = useState(null); + const [editingRegionName, setEditingRegionName] = useState(""); + const [editingCountryCode, setEditingCountryCode] = useState(null); + const [editingCountryName, setEditingCountryName] = useState(""); + const [editingCountryRegionCode, setEditingCountryRegionCode] = useState(""); + + const regionsQuery = useQuery({ + queryKey: ["admin", "regions"], + queryFn: adminApi.getRegions, + placeholderData: keepPreviousData, + }); + + const countriesQuery = useQuery({ + queryKey: ["admin", "countries"], + queryFn: adminApi.getCountries, + placeholderData: keepPreviousData, + }); + + const regions = useMemo(() => normalizeCollection(regionsQuery.data), [regionsQuery.data]); + const countries = useMemo(() => normalizeCollection(countriesQuery.data), [countriesQuery.data]); + const regionOptions = useMemo( + () => + regions + .map((region) => ({ code: getRegionCode(region), name: getRegionName(region) })) + .filter((region): region is { code: string; name: string | undefined } => Boolean(region.code)), + [regions], + ); + + useEffect(() => { + if (!countryRegionCode && regionOptions[0]?.code) { + setCountryRegionCode(regionOptions[0].code); + } + }, [countryRegionCode, regionOptions]); + + useEffect(() => { + if (regionsQuery.isError) { + toast.error("권역 목록을 불러오지 못했습니다."); + } + }, [regionsQuery.isError]); + + useEffect(() => { + if (countriesQuery.isError) { + toast.error("지역 목록을 불러오지 못했습니다."); + } + }, [countriesQuery.isError]); + + const invalidateRegions = async () => { + await queryClient.invalidateQueries({ queryKey: ["admin", "regions"] }); + }; + + const invalidateCountries = async () => { + await queryClient.invalidateQueries({ queryKey: ["admin", "countries"] }); + }; + + const createRegionMutation = useMutation({ + mutationFn: adminApi.createRegion, + onSuccess: async () => { + await invalidateRegions(); + setRegionCode(""); + setRegionName(""); + toast.success("권역을 생성했습니다."); + }, + onError: () => toast.error("권역 생성에 실패했습니다."), + }); + + const updateRegionMutation = useMutation({ + mutationFn: ({ code, koreanName }: { code: string; koreanName: string }) => + adminApi.updateRegion(code, { koreanName }), + onSuccess: async () => { + await invalidateRegions(); + setEditingRegionCode(null); + setEditingRegionName(""); + toast.success("권역을 수정했습니다."); + }, + onError: () => toast.error("권역 수정에 실패했습니다."), + }); + + const deleteRegionMutation = useMutation({ + mutationFn: adminApi.deleteRegion, + onSuccess: async () => { + await Promise.all([invalidateRegions(), invalidateCountries()]); + toast.success("권역을 삭제했습니다."); + }, + onError: () => toast.error("권역 삭제에 실패했습니다."), + }); + + const createCountryMutation = useMutation({ + mutationFn: adminApi.createCountry, + onSuccess: async () => { + await invalidateCountries(); + setCountryCode(""); + setCountryName(""); + setCountryRegionCode(regionOptions[0]?.code ?? ""); + toast.success("지역을 생성했습니다."); + }, + onError: () => toast.error("지역 생성에 실패했습니다."), + }); + + const updateCountryMutation = useMutation({ + mutationFn: ({ code, koreanName, regionCode }: { code: string; koreanName: string; regionCode: string }) => + adminApi.updateCountry(code, { koreanName, regionCode }), + onSuccess: async () => { + await invalidateCountries(); + setEditingCountryCode(null); + setEditingCountryName(""); + setEditingCountryRegionCode(""); + toast.success("지역을 수정했습니다."); + }, + onError: () => toast.error("지역 수정에 실패했습니다."), + }); + + const deleteCountryMutation = useMutation({ + mutationFn: adminApi.deleteCountry, + onSuccess: async () => { + await invalidateCountries(); + toast.success("지역을 삭제했습니다."); + }, + onError: () => toast.error("지역 삭제에 실패했습니다."), + }); + + const isRegionMutating = + createRegionMutation.isPending || updateRegionMutation.isPending || deleteRegionMutation.isPending; + const isCountryMutating = + createCountryMutation.isPending || updateCountryMutation.isPending || deleteCountryMutation.isPending; + + const handleCreateRegion = (event: FormEvent) => { + event.preventDefault(); + const normalizedCode = regionCode.trim(); + const normalizedName = regionName.trim(); + + if (!normalizedCode || !normalizedName) { + toast.error("권역 코드와 이름을 입력해주세요."); + return; + } + + createRegionMutation.mutate({ code: normalizedCode, koreanName: normalizedName }); + }; + + const handleCreateCountry = (event: FormEvent) => { + event.preventDefault(); + const normalizedCode = countryCode.trim(); + const normalizedName = countryName.trim(); + const normalizedRegionCode = countryRegionCode.trim(); + + if (!normalizedCode || !normalizedName || !normalizedRegionCode) { + toast.error("지역 코드, 이름, 권역 코드를 입력해주세요."); + return; + } + + createCountryMutation.mutate({ + code: normalizedCode, + koreanName: normalizedName, + regionCode: normalizedRegionCode, + }); + }; + + const handleStartEditRegion = (region: RegionResponse) => { + const code = getRegionCode(region); + if (!code) { + toast.error("권역 코드를 확인할 수 없습니다."); + return; + } + + setEditingRegionCode(code); + setEditingRegionName(getRegionName(region) ?? ""); + }; + + const handleUpdateRegion = (code: string) => { + const normalizedName = editingRegionName.trim(); + if (!normalizedName) { + toast.error("권역 이름을 입력해주세요."); + return; + } + + updateRegionMutation.mutate({ code, koreanName: normalizedName }); + }; + + const handleDeleteRegion = (code: string) => { + const confirmed = window.confirm(`권역 ${code}를 삭제할까요? 연결된 지역에 영향이 있을 수 있습니다.`); + if (!confirmed) return; + + deleteRegionMutation.mutate(code); + }; + + const handleStartEditCountry = (country: CountryResponse) => { + const code = getCountryCode(country); + if (!code) { + toast.error("지역 코드를 확인할 수 없습니다."); + return; + } + + setEditingCountryCode(code); + setEditingCountryName(getCountryName(country) ?? ""); + setEditingCountryRegionCode(getCountryRegionCode(country) ?? ""); + }; + + const handleUpdateCountry = (code: string) => { + const normalizedName = editingCountryName.trim(); + const normalizedRegionCode = editingCountryRegionCode.trim(); + + if (!normalizedName || !normalizedRegionCode) { + toast.error("지역 이름과 권역 코드를 입력해주세요."); + return; + } + + updateCountryMutation.mutate({ code, koreanName: normalizedName, regionCode: normalizedRegionCode }); + }; + + const handleDeleteCountry = (code: string) => { + const confirmed = window.confirm(`지역 ${code}를 삭제할까요?`); + if (!confirmed) return; + + deleteCountryMutation.mutate(code); + }; + + return ( + + + + + + 권역 + 예: EUROPE, AMERICAS + + 총 {regions.length.toLocaleString()}건 + + + + setRegionCode(event.target.value)} placeholder="권역 코드" /> + setRegionName(event.target.value)} placeholder="권역 이름" /> + + 생성 + + + + + + + + 코드 + 이름 + 작업 + + + + {regionsQuery.isLoading ? ( + + + 권역을 불러오는 중... + + + ) : regionsQuery.isError ? ( + + + 권역을 불러오지 못했습니다. + + + ) : regions.length === 0 ? ( + + + 권역이 없습니다. + + + ) : ( + regions.map((region, index) => { + const code = getRegionCode(region); + const isEditing = Boolean(code && editingRegionCode === code); + + return ( + + {toDisplayText(code)} + + {isEditing ? ( + setEditingRegionName(event.target.value)} + /> + ) : ( + toDisplayText(getRegionName(region)) + )} + + + {isEditing && code ? ( + + handleUpdateRegion(code)} + disabled={updateRegionMutation.isPending} + > + 저장 + + setEditingRegionCode(null)}> + 취소 + + + ) : ( + + handleStartEditRegion(region)} + disabled={!code} + > + 수정 + + code && handleDeleteRegion(code)} + disabled={!code || isRegionMutating} + > + 삭제 + + + )} + + + ); + }) + )} + + + + + + + + + 지역 + 예: AT, US, JP + + 총 {countries.length.toLocaleString()}건 + + + + setCountryCode(event.target.value)} + placeholder="지역 코드" + /> + setCountryName(event.target.value)} + placeholder="지역 이름" + /> + setCountryRegionCode(event.target.value)} + className="h-9 rounded-md border border-k-200 bg-k-0 px-3 typo-regular-4 text-k-700 outline-none focus-visible:border-primary" + > + 권역 선택 + {regionOptions.map((region) => ( + + {region.name ? `${region.name} (${region.code})` : region.code} + + ))} + + + 생성 + + + + + + + + 코드 + 이름 + 권역 + 작업 + + + + {countriesQuery.isLoading ? ( + + + 지역을 불러오는 중... + + + ) : countriesQuery.isError ? ( + + + 지역을 불러오지 못했습니다. + + + ) : countries.length === 0 ? ( + + + 지역이 없습니다. + + + ) : ( + countries.map((country, index) => { + const code = getCountryCode(country); + const isEditing = Boolean(code && editingCountryCode === code); + + return ( + + {toDisplayText(code)} + + {isEditing ? ( + setEditingCountryName(event.target.value)} + /> + ) : ( + toDisplayText(getCountryName(country)) + )} + + + {isEditing ? ( + setEditingCountryRegionCode(event.target.value)} + className="h-9 min-w-[150px] rounded-md border border-k-200 bg-k-0 px-3 typo-regular-4 text-k-700 outline-none focus-visible:border-primary" + > + 권역 선택 + {regionOptions.map((region) => ( + + {region.name ? `${region.name} (${region.code})` : region.code} + + ))} + + ) : ( + toDisplayText(getCountryRegionCode(country)) + )} + + + {isEditing && code ? ( + + handleUpdateCountry(code)} + disabled={updateCountryMutation.isPending} + > + 저장 + + setEditingCountryCode(null)}> + 취소 + + + ) : ( + + handleStartEditCountry(country)} + disabled={!code} + > + 수정 + + code && handleDeleteCountry(code)} + disabled={!code || isCountryMutating} + > + 삭제 + + + )} + + + ); + }) + )} + + + + + + + ); +} diff --git a/apps/admin/src/components/layout/AdminSidebar.tsx b/apps/admin/src/components/layout/AdminSidebar.tsx index c6bab38b..d4aadf8a 100644 --- a/apps/admin/src/components/layout/AdminSidebar.tsx +++ b/apps/admin/src/components/layout/AdminSidebar.tsx @@ -1,11 +1,12 @@ -import { FileText, FlaskConical, MessageSquare, UserCheck } from "lucide-react"; +import { FileText, FlaskConical, MapPinned, MessageSquare, UserCheck } from "lucide-react"; import { cn } from "@/lib/utils"; -export type ActiveAdminMenu = "scores" | "mentorApplications" | "bruno" | "chatSocket"; +export type ActiveAdminMenu = "scores" | "mentorApplications" | "regionsCountries" | "bruno" | "chatSocket"; const sideMenus = [ { key: "scores", label: "성적 관리", icon: FileText, to: "/scores" as const }, { key: "mentorApplications", label: "멘토 승격 요청", icon: UserCheck, to: "/mentor-applications" as const }, + { key: "regionsCountries", label: "권역/지역 관리", icon: MapPinned, to: "/regions-countries" as const }, { key: "bruno", label: "Bruno API", icon: FlaskConical, to: "/bruno" as const }, { key: "chatSocket", label: "채팅 소켓", icon: MessageSquare, to: "/chat-socket" as const }, ] as const; diff --git a/apps/admin/src/lib/api/admin.ts b/apps/admin/src/lib/api/admin.ts index a8373dba..6ebad569 100644 --- a/apps/admin/src/lib/api/admin.ts +++ b/apps/admin/src/lib/api/admin.ts @@ -5,6 +5,29 @@ import type { MentorApplicationStatus, } from "@/types/mentorApplications"; +export interface AdminCollectionResponse { + content?: T[]; + data?: T[]; + items?: T[]; + result?: T[]; +} + +export type AdminCollection = T[] | AdminCollectionResponse; + +export interface MentorApplicationCountItem { + mentorApplicationStatus?: MentorApplicationStatus | string | null; + status?: MentorApplicationStatus | string | null; + name?: string | null; + count?: number | string | null; + total?: number | string | null; + value?: number | string | null; +} + +export type MentorApplicationCountResponse = + | Partial> + | MentorApplicationCountItem[] + | AdminCollectionResponse; + export interface MentorApplicationListParams { page?: number; size?: number; @@ -13,25 +36,43 @@ export interface MentorApplicationListParams { createdAt?: string; } +export interface RegionResponse { + code?: string | null; + regionCode?: string | null; + koreanName?: string | null; + name?: string | null; +} + export interface RegionPayload { code?: string; koreanName: string; } +export interface CountryResponse { + code?: string | null; + koreanName?: string | null; + name?: string | null; + regionCode?: string | null; + region?: string | RegionResponse | null; +} + export interface CountryPayload { code?: string; koreanName: string; regionCode: string; } +const assignMentorApplicationUniversity = (mentorApplicationId: string | number, universityId: number) => + axiosInstance + .post(`/admin/mentor-applications/${mentorApplicationId}/assign-university`, { universityId }) + .then((res) => res.data); + export const adminApi = { getMentorApplicationList: (params: MentorApplicationListParams) => axiosInstance.get("/admin/mentor-applications", { params }).then((res) => res.data), getCountMentorApplicationByStatus: () => - axiosInstance - .get>>("/admin/mentor-applications/count") - .then((res) => res.data), + axiosInstance.get("/admin/mentor-applications/count").then((res) => res.data), getMentorApplicationHistoryList: (siteUserId: string | number) => axiosInstance @@ -46,12 +87,31 @@ export const adminApi = { .post(`/admin/mentor-applications/${mentorApplicationId}/reject`, { rejectedReason }) .then((res) => res.data), - postMappingMentorapplicationUniversity: (mentorApplicationId: string | number, universityId: number) => - axiosInstance - .post(`/admin/mentor-applications/${mentorApplicationId}/assign-university`, { universityId }) - .then((res) => res.data), + postMappingMentorapplicationUniversity: assignMentorApplicationUniversity, + + assignMentorApplicationUniversity, + + getRegions: () => axiosInstance.get>("/admin/regions").then((res) => res.data), + + createRegion: (data: RegionPayload) => + axiosInstance.post("/admin/regions", data).then((res) => res.data), + + updateRegion: (code: string, data: RegionPayload) => + axiosInstance.put(`/admin/regions/${code}`, data).then((res) => res.data), + + deleteRegion: (code: string) => axiosInstance.delete(`/admin/regions/${code}`).then((res) => res.data), + + getCountries: () => axiosInstance.get>("/admin/countries").then((res) => res.data), + + createCountry: (data: CountryPayload) => + axiosInstance.post("/admin/countries", data).then((res) => res.data), + + updateCountry: (code: string, data: CountryPayload) => + axiosInstance.put(`/admin/countries/${code}`, data).then((res) => res.data), + + deleteCountry: (code: string) => axiosInstance.delete(`/admin/countries/${code}`).then((res) => res.data), - get권역조회: () => axiosInstance.get("/admin/regions").then((res) => res.data), + get권역조회: () => axiosInstance.get>("/admin/regions").then((res) => res.data), post권역생성: (data: RegionPayload) => axiosInstance.post("/admin/regions", data).then((res) => res.data), @@ -60,7 +120,7 @@ export const adminApi = { delete권역삭제: (code: string) => axiosInstance.delete(`/admin/regions/${code}`).then((res) => res.data), - get지역조회: () => axiosInstance.get("/admin/countries").then((res) => res.data), + get지역조회: () => axiosInstance.get>("/admin/countries").then((res) => res.data), post지역생성: (data: CountryPayload) => axiosInstance.post("/admin/countries", data).then((res) => res.data),
{option.label}
+ {countQuery.isLoading ? "..." : typeof count === "number" ? count.toLocaleString() : "-"} +
{toDisplayText(getCountry(application))} / {toDisplayText(getTerm(application))}
대학 ID {toDisplayText(universityId)}
예: EUROPE, AMERICAS
총 {regions.length.toLocaleString()}건
예: AT, US, JP
총 {countries.length.toLocaleString()}건