feat: add platform access sync action

This commit is contained in:
2026-08-13 09:53:45 +09:00
parent c813325b54
commit 73a0820b1d
2 changed files with 40 additions and 1 deletions
+12
View File
@@ -269,6 +269,18 @@ export async function assignInstructorPlatform(
return response.json() as Promise<InstructorPlatformsResponse>;
}
export async function syncInstructorPlatformAccess(accessToken: string): Promise<{ ok: boolean; assignmentCount: number; syncedAt: string }> {
const response = await fetch(`${LMS_API_BASE_URL}/instructor/platforms/access-sync`, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!response.ok) {
throw new Error(await platformErrorMessage(response, '플랫폼 접근 설정을 동기화하지 못했습니다.'));
}
return response.json() as Promise<{ ok: boolean; assignmentCount: number; syncedAt: string }>;
}
async function platformErrorMessage(response: Response, fallback: string) {
const payload = await response.json().catch((): null => null);
return platformErrorMessageFromPayload(response, payload, fallback);
+28 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { Activity, Battery, Car, CircleAlert, KeyRound, Loader2, Plus, Search, Settings, ShieldAlert, Timer, Trash2, Users, X, type LucideIcon } from 'lucide-react';
import { Activity, Battery, Car, CircleAlert, KeyRound, Loader2, Plus, RefreshCw, Search, Settings, ShieldAlert, Timer, Trash2, Users, X, type LucideIcon } from 'lucide-react';
import { ConfirmDialog } from '../../components/ConfirmDialog';
import { PageHeader } from '../../components/PageHeader';
import { PaginationBar } from '../../components/PaginationBar';
@@ -12,6 +12,7 @@ import {
listInstructorPlatformAgents,
listInstructorPlatforms,
issueInstructorPlatformRegistration,
syncInstructorPlatformAccess,
PlatformInUseError,
updateInstructorPlatform,
type InstructorPlatform,
@@ -95,6 +96,7 @@ export function PlatformsPage({ session }: { session: AuthSession }) {
const [assignmentGroupsError, setAssignmentGroupsError] = useState<string | null>(null);
const [selectedAssignmentGroupId, setSelectedAssignmentGroupId] = useState('');
const [assigningPlatformId, setAssigningPlatformId] = useState<string | null>(null);
const [syncingAccess, setSyncingAccess] = useState(false);
const [forceAssignmentTarget, setForceAssignmentTarget] = useState<{ platform: InstructorPlatform; group: InstructorGroup | null; session: PlatformInUseError['activeSession'] } | null>(null);
const filteredPlatforms = useMemo(() => {
@@ -262,6 +264,22 @@ export function PlatformsPage({ session }: { session: AuthSession }) {
}
};
const handleSyncPlatformAccess = async () => {
setSyncingAccess(true);
setError(null);
try {
const result = await syncInstructorPlatformAccess(session.accessToken);
showToast({
title: '플랫폼 접근 설정을 동기화했습니다.',
message: `현재 배정 기준 ${result.assignmentCount}개 플랫폼을 반영했습니다.`,
});
} catch (reason) {
setError(reason instanceof Error ? reason.message : '플랫폼 접근 설정을 동기화하지 못했습니다.');
} finally {
setSyncingAccess(false);
}
};
const handleDeletePlatform = async (platform: InstructorPlatform, force = false) => {
setDeletingPlatformId(platform.id);
setError(null);
@@ -378,6 +396,15 @@ export function PlatformsPage({ session }: { session: AuthSession }) {
route="platforms"
action={
<div className="flex items-center gap-2">
<button
className="inline-flex h-9 items-center gap-2 rounded-md border border-[#d1d9e6] bg-white px-3 text-[13px] font-bold text-[#475569] hover:bg-[#f8fafc] disabled:cursor-not-allowed disabled:opacity-60"
type="button"
onClick={handleSyncPlatformAccess}
disabled={syncingAccess}
>
{syncingAccess ? <Loader2 className="animate-spin" size={15} aria-hidden="true" /> : <RefreshCw size={15} aria-hidden="true" />}
접근 동기화
</button>
<button
className="inline-flex h-9 items-center gap-2 rounded-md border border-[#2563eb] bg-white px-3 text-[13px] font-bold text-[#2563eb] hover:bg-[#eff6ff]"
type="button"