From 679c1656f400586d2e03ed429f16b64faccc7e56 Mon Sep 17 00:00:00 2001 From: chan Date: Wed, 10 Jun 2026 17:30:12 +0900 Subject: [PATCH] =?UTF-8?q?adminfront:=20React=20Query=20Optimistic=20Upda?= =?UTF-8?q?tes=20=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=84=B8=EB=B6=80?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EB=A7=A4=ED=8A=B8=EB=A6=AD=EC=8A=A4=20?= =?UTF-8?q?UI=200ms=20=EC=A6=89=EA=B0=81=20=EB=B0=98=EC=9D=91=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=86=8D=EB=8F=84=20=EA=B3=A0=EB=8F=84?= =?UTF-8?q?=ED=99=94=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TenantFineGrainedPermissionsPage.tsx | 62 +++++++++++++++++-- .../TenantFineGrainedPermissionsTab.tsx | 62 +++++++++++++++++-- 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx index dc47f741..7542716f 100644 --- a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx @@ -75,24 +75,74 @@ export function TenantFineGrainedPermissionsPage() { const addSystemRelationMutation = useMutation({ mutationFn: (payload: { userId: string; relation: string }) => addSystemRelation(payload.userId, payload.relation), + onMutate: async (newRelation) => { + await queryClient.cancelQueries({ queryKey: ["system-relations"] }); + const previousRelations = queryClient.getQueryData(["system-relations"]); + + queryClient.setQueryData(["system-relations"], (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === newRelation.userId) { + return { + ...user, + relations: user.relations.includes(newRelation.relation) + ? user.relations + : [...user.relations, newRelation.relation], + }; + } + return user; + }); + }); + + return { previousRelations }; + }, + onError: (err: AxiosError<{ error?: string }>, _, context) => { + if (context?.previousRelations) { + queryClient.setQueryData(["system-relations"], context.previousRelations); + } + toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["system-relations"] }); toast.success(t("msg.admin.system.relations.add_success", "시스템 메뉴 권한이 추가되었습니다.")); }, - onError: (err: AxiosError<{ error?: string }>) => { - toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["system-relations"] }); }, }); const removeSystemRelationMutation = useMutation({ mutationFn: (payload: { userId: string; relation: string }) => removeSystemRelation(payload.userId, payload.relation), + onMutate: async (targetRelation) => { + await queryClient.cancelQueries({ queryKey: ["system-relations"] }); + const previousRelations = queryClient.getQueryData(["system-relations"]); + + queryClient.setQueryData(["system-relations"], (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === targetRelation.userId) { + return { + ...user, + relations: user.relations.filter((r) => r !== targetRelation.relation), + }; + } + return user; + }); + }); + + return { previousRelations }; + }, + onError: (err: AxiosError<{ error?: string }>, _, context) => { + if (context?.previousRelations) { + queryClient.setQueryData(["system-relations"], context.previousRelations); + } + toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["system-relations"] }); toast.success(t("msg.admin.system.relations.remove_success", "시스템 메뉴 권한이 회수되었습니다.")); }, - onError: (err: AxiosError<{ error?: string }>) => { - toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["system-relations"] }); }, }); diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx index 9f801e8a..7c4eac3d 100644 --- a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx @@ -68,24 +68,74 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai const addRelationMutation = useMutation({ mutationFn: (payload: { userId: string; relation: string }) => addTenantRelation(tenantId, payload.userId, payload.relation), + onMutate: async (newRelation) => { + await queryClient.cancelQueries({ queryKey: ["tenant-relations", tenantId] }); + const previousRelations = queryClient.getQueryData(["tenant-relations", tenantId]); + + queryClient.setQueryData(["tenant-relations", tenantId], (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === newRelation.userId) { + return { + ...user, + relations: user.relations.includes(newRelation.relation) + ? user.relations + : [...user.relations, newRelation.relation], + }; + } + return user; + }); + }); + + return { previousRelations }; + }, + onError: (err: AxiosError<{ error?: string }>, _, context) => { + if (context?.previousRelations) { + queryClient.setQueryData(["tenant-relations", tenantId], context.previousRelations); + } + toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["tenant-relations", tenantId] }); toast.success(t("msg.admin.tenants.relations.add_success", "세부 권한이 추가되었습니다.")); }, - onError: (err: AxiosError<{ error?: string }>) => { - toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["tenant-relations", tenantId] }); }, }); const removeRelationMutation = useMutation({ mutationFn: (payload: { userId: string; relation: string }) => removeTenantRelation(tenantId, payload.userId, payload.relation), + onMutate: async (targetRelation) => { + await queryClient.cancelQueries({ queryKey: ["tenant-relations", tenantId] }); + const previousRelations = queryClient.getQueryData(["tenant-relations", tenantId]); + + queryClient.setQueryData(["tenant-relations", tenantId], (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === targetRelation.userId) { + return { + ...user, + relations: user.relations.filter((r) => r !== targetRelation.relation), + }; + } + return user; + }); + }); + + return { previousRelations }; + }, + onError: (err: AxiosError<{ error?: string }>, _, context) => { + if (context?.previousRelations) { + queryClient.setQueryData(["tenant-relations", tenantId], context.previousRelations); + } + toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["tenant-relations", tenantId] }); toast.success(t("msg.admin.tenants.relations.remove_success", "세부 권한이 회수되었습니다.")); }, - onError: (err: AxiosError<{ error?: string }>) => { - toast.error(err.response?.data?.error || t("msg.common.error", "오류가 발생했습니다.")); + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["tenant-relations", tenantId] }); }, });