Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/services/familyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface Permission {
permissionId: number;
permissionTitle: string;
createdAt: string;
is_enable: boolean;
}

interface MemberPermissionsResponse {
Expand Down
8 changes: 4 additions & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,18 @@ export default function Header({
className={`h-16 ${darkMode ? "invert" : ""}`}
/>
) : (
<h1 className="font-semibold text-[#333333] m-0 text-lg">
<h1 className="h-16 flex items-center font-semibold text-[#333333] m-0 text-lg">
{getPageTitle()}
</h1>
)}
</div>

<div className="flex items-center gap-3 w-20 justify-end">
<div className="flex items-center gap-2 w-20 justify-end">
{showAlarmIcon && (
<button
type="button"
aria-label="알림"
className={`relative cursor-pointer flex items-center justify-center w-12 h-12 rounded-full shrink-0 border-[3px] border-white ${darkMode ? "bg-white invert" : "bg-gradient-to-b from-white/0 to-white/100 to-42%"}`}
className={`relative cursor-pointer flex items-center justify-center w-11 h-11 rounded-full shrink-0 border-[3px] border-white ${darkMode ? "bg-white invert" : "bg-gradient-to-b from-white/0 to-white/100 to-42%"}`}
onClick={() => navigate("/alarm")}
>
<img src={alarmIcon} alt="" className="w-6 h-6" />
Expand All @@ -146,7 +146,7 @@ export default function Header({
<button
type="button"
aria-label="설정"
className={`cursor-pointer flex items-center justify-center w-12 h-12 rounded-full shrink-0 border-[3px] border-white ${darkMode ? "bg-white invert" : "bg-gradient-to-b from-white/0 to-white/100 to-42%"}`}
className={`cursor-pointer flex items-center justify-center w-11 h-11 rounded-full shrink-0 border-[3px] border-white ${darkMode ? "bg-white invert" : "bg-gradient-to-b from-white/0 to-white/100 to-42%"}`}
onClick={() => navigate("/setting")}
>
<img src={settingIcon} alt="" className="w-6 h-6" />
Expand Down
22 changes: 7 additions & 15 deletions src/page/Detail/DetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ export default function Detail() {
try {
const { data } = await familyService.getMyPermissions();
const hasPerm = data.memberPermissions.some(
(p) => p.permissionTitle === "앱 사용량 비공개 허용 권한",
(p) =>
p.permissionTitle === "앱 사용량 비공개 허용 권한" && p.is_enable,
);
setHasPrivacyPermission(hasPerm);
if (!hasPerm) setGlobalIsPublic(true);
Expand Down Expand Up @@ -160,23 +161,13 @@ export default function Detail() {
const parsed = parseAppUsageResponse(appRes, fallback);

if (parsed) {
// 본인 + 권한 없으면 isPublic 강제 true
if (isOwnData && !hasPrivacyPermission) {
parsed.data.isPublic = true;
}
// 다른 사람 데이터: API의 isPublic 그대로 사용
// isPublic: false → 비공개 자물쇠 UI 표시
setAppUsage(parsed.data);
if (isOwnData && !hasPrivacyPermission) {
setGlobalIsPublic(true);
} else if (parsed.updatedIsPublic != null) {
// API의 isPublic 값 그대로 반영 (본인/타인 모두 동일)
if (parsed.updatedIsPublic != null) {
setGlobalIsPublic(parsed.updatedIsPublic);
} else if (loading) {
setGlobalIsPublic(true);
}
} else {
setAppUsage(emptyAppUsage(fallback));
if (loading) setGlobalIsPublic(true);
}
} catch {
setAppUsage(emptyAppUsage(globalIsPublic));
Expand Down Expand Up @@ -205,7 +196,7 @@ export default function Detail() {
} catch {
// polling 실패는 무시
}
}, 1000);
}, 10000);

return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -381,7 +372,8 @@ export default function Detail() {
isPublic={appUsage.isPublic}
onPublicToggle={handleVisibilityToggle}
disableToggle={!hasPrivacyPermission}
showToggle={isOwnData}
showToggle={isOwnData && hasPrivacyPermission}
isOwnData={isOwnData}
/>
</motion.div>
</motion.div>
Expand Down
6 changes: 5 additions & 1 deletion src/page/Detail/components/AppUsageChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface AppUsageChartProps {
onPublicToggle: (value: boolean) => void;
disableToggle?: boolean;
showToggle?: boolean;
isOwnData?: boolean;
}

const appNameMap: Record<string, string> = {
Expand Down Expand Up @@ -56,13 +57,16 @@ export default function AppUsageChart({
onPublicToggle,
disableToggle = false,
showToggle = true,
isOwnData = false,
}: AppUsageChartProps) {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [animated, setAnimated] = useState(false);
const ref = useRef<HTMLDivElement>(null);

// apps가 null이거나 undefined일 경우 빈 배열로 처리
const safeApps = apps || [];
// 본인이면 isPublic=false여도 데이터 보여주기
const shouldShowData = isOwnData || isPublic;

// 10개 초과 시 상위 10개 + 나머지를 "기타"로 묶기
const MAX_DISPLAY = 7;
Expand Down Expand Up @@ -133,7 +137,7 @@ export default function AppUsageChart({
</div>

<div className="relative">
{!isPublic ? (
{!shouldShowData ? (
<div className="flex flex-col items-center justify-center py-16">
<svg
width="48"
Expand Down
8 changes: 4 additions & 4 deletions src/page/Main/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ export default function Main() {
useQuery<FamilyApiResponse>({
queryKey: ["familyMembers"],
queryFn: () => familyService.getMembers().then((res) => res.data),
refetchInterval: 1000,
refetchInterval: 10000,
refetchIntervalInBackground: true,
placeholderData: keepPreviousData, // ← v5 방식
});

const { data: usageData } = useQuery<UsageData>({
queryKey: ["UsageData"],
queryFn: () => sharedPoolService.getUsageData().then((res) => res.data),
refetchInterval: 1000, // 10초마다 자동 폴링
refetchInterval: 10000, // 10초마다 자동 폴링
refetchIntervalInBackground: true, // 백그라운드에서도 폴링
placeholderData: keepPreviousData,
});
Expand Down Expand Up @@ -97,7 +97,7 @@ export default function Main() {
useQuery<SharedData>({
queryKey: ["sharedPool"],
queryFn: () => sharedPoolService.getMainRemainingAmount(),
refetchInterval: 1000, // 10초마다 자동 폴링
refetchInterval: 10000, // 10초마다 자동 폴링
refetchIntervalInBackground: true, // 백그라운드에서도 폴링
placeholderData: keepPreviousData,
});
Expand All @@ -109,7 +109,7 @@ export default function Main() {
queryKey: ["blockStatus", lineId],
queryFn: () => blockService.getBlockStatus(lineId!).then((res) => res.data),
enabled: !!lineId,
refetchInterval: 1000, // 10초마다 자동 폴링
refetchInterval: 10000, // 10초마다 자동 폴링
refetchIntervalInBackground: true, // 백그라운드에서도 폴링
placeholderData: keepPreviousData,
});
Expand Down
33 changes: 31 additions & 2 deletions src/page/Policy/components/Permisssion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Toggle from "@/components/common/Toggle";
import { permissionService } from "@/api/services/permissionService";
import type { PatchPermissionRequest } from "@/types/permission";
import { useToastStore } from "@/store/toastStore";
import { familyService } from "@/api";

const PERMISSION_VIEW_DETAIL = "상세페이지 열람 권한";
const PERMISSION_HIDE_APP_USAGE = "앱 사용량 비공개 허용 권한";
Expand Down Expand Up @@ -128,6 +129,17 @@ function PermissionManager() {
patchPermissions(changedPayload);
};

// 이미 캐시에 있는 데이터 가져오기
const { data: familyMembers = [] } = useQuery({
queryKey: ["familyMembersSimple"],
queryFn: () => familyService.getMembersSimple().then((res) => res.data),
staleTime: Infinity, // 이미 있으면 재요청 안함
});

// lineId로 phone 찾기
const getPhone = (lineId: number) =>
familyMembers.find((m) => m.lineId === lineId)?.phone;

return (
<>
<GlassCard
Expand Down Expand Up @@ -178,7 +190,16 @@ function PermissionManager() {
{rows.map((row, index) => (
<div key={row.lineId}>
<div className="grid grid-cols-3 items-center py-3 px-2">
<span className="text-sm text-gray-700">{row.userName}</span>
<span className="text-sm text-gray-700">
{row.userName}
{/* 같은 이름이 여러 명이면 번호 표시 */}
{rows.filter((r) => r.userName === row.userName).length >
1 && (
<span className="text-xs text-gray-400 ml-1">
({getPhone(row.lineId)?.slice(-4)})
</span>
)}
</span>
<div className="flex justify-center">
<Toggle
checked={row.canViewDetail}
Expand Down Expand Up @@ -226,7 +247,15 @@ function PermissionManager() {
className="flex items-center justify-between text-sm text-gray-600 bg-gray-50 rounded-lg px-3 py-2"
>
<span>
{row?.userName} · {permissionName}
{row?.userName}
{rows.filter((r) => r.userName === row?.userName).length >
1 && (
<span className="text-xs text-gray-400 ml-1">
({getPhone(row!.lineId)?.slice(-4)})
</span>
)}
{" · "}
{permissionName}
</span>
<span
className="font-semibold"
Expand Down
Loading