feat: add platform access sync action
This commit is contained in:
@@ -269,6 +269,18 @@ export async function assignInstructorPlatform(
|
|||||||
return response.json() as Promise<InstructorPlatformsResponse>;
|
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) {
|
async function platformErrorMessage(response: Response, fallback: string) {
|
||||||
const payload = await response.json().catch((): null => null);
|
const payload = await response.json().catch((): null => null);
|
||||||
return platformErrorMessageFromPayload(response, payload, fallback);
|
return platformErrorMessageFromPayload(response, payload, fallback);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
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 { ConfirmDialog } from '../../components/ConfirmDialog';
|
||||||
import { PageHeader } from '../../components/PageHeader';
|
import { PageHeader } from '../../components/PageHeader';
|
||||||
import { PaginationBar } from '../../components/PaginationBar';
|
import { PaginationBar } from '../../components/PaginationBar';
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
listInstructorPlatformAgents,
|
listInstructorPlatformAgents,
|
||||||
listInstructorPlatforms,
|
listInstructorPlatforms,
|
||||||
issueInstructorPlatformRegistration,
|
issueInstructorPlatformRegistration,
|
||||||
|
syncInstructorPlatformAccess,
|
||||||
PlatformInUseError,
|
PlatformInUseError,
|
||||||
updateInstructorPlatform,
|
updateInstructorPlatform,
|
||||||
type InstructorPlatform,
|
type InstructorPlatform,
|
||||||
@@ -95,6 +96,7 @@ export function PlatformsPage({ session }: { session: AuthSession }) {
|
|||||||
const [assignmentGroupsError, setAssignmentGroupsError] = useState<string | null>(null);
|
const [assignmentGroupsError, setAssignmentGroupsError] = useState<string | null>(null);
|
||||||
const [selectedAssignmentGroupId, setSelectedAssignmentGroupId] = useState('');
|
const [selectedAssignmentGroupId, setSelectedAssignmentGroupId] = useState('');
|
||||||
const [assigningPlatformId, setAssigningPlatformId] = useState<string | null>(null);
|
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 [forceAssignmentTarget, setForceAssignmentTarget] = useState<{ platform: InstructorPlatform; group: InstructorGroup | null; session: PlatformInUseError['activeSession'] } | null>(null);
|
||||||
|
|
||||||
const filteredPlatforms = useMemo(() => {
|
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) => {
|
const handleDeletePlatform = async (platform: InstructorPlatform, force = false) => {
|
||||||
setDeletingPlatformId(platform.id);
|
setDeletingPlatformId(platform.id);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -378,6 +396,15 @@ export function PlatformsPage({ session }: { session: AuthSession }) {
|
|||||||
route="platforms"
|
route="platforms"
|
||||||
action={
|
action={
|
||||||
<div className="flex items-center gap-2">
|
<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
|
<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]"
|
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"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user