Skip to content
Closed
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
52 changes: 41 additions & 11 deletions src/api/Myplace/saved.api.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@

import api from "../api";
import type { ApiResponse } from "@/types/api-response";

export type PlaceSaveResult = {
placeId: number;
typeId?: number;
liked: boolean;
changed: boolean;
memo?: string | null;
createdAt: string;
updatedAt: string;
placedId: number;
type: string;
enabled: boolean;
changed: boolean;
likeCount: number;
message: string;
createdAt: string;
updatedAt: string;
};
export type PlaceActionRequestDto = {
contentId: string | number;
regionName: string;
themeName: string;
cnctrLevel: number;
enabled?: boolean;
action?: "UNSAVE" | "SAVE";
};

function unwrap<T>(raw: T | ApiResponse<T>): T {
const any = raw as any;
return any && typeof any === "object" && "data" in any && any.data != null
? (any.data as T)
: (raw as T);
}

export async function unsavePlace(
contentId: string | number
dto: PlaceActionRequestDto
): Promise<PlaceSaveResult> {
const { data } = await api.delete<ApiResponse<PlaceSaveResult>>(
const body: PlaceActionRequestDto = {
...dto,
cnctrLevel: Number(dto.cnctrLevel),
enabled: dto.enabled ?? false,
action: dto.action ?? "UNSAVE",
};

const { data } = await api.delete<ApiResponse<PlaceSaveResult> | PlaceSaveResult>(
"/my/places/save",
{ params: { contentId: String(contentId) } }
{
data: body,
headers: { "Content-Type": "application/json" },
}
);
return data.data;

return unwrap<PlaceSaveResult>(data);
}

export default { unsavePlace };
22 changes: 0 additions & 22 deletions src/api/Myplace/saved.ts

This file was deleted.

183 changes: 175 additions & 8 deletions src/api/Myplace/saveg.api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,90 @@
import api from "../api";
import type { ApiResponse } from "@/types/api-response";

export type SavedPlaceSnapshot = {
contentId: string | number;
title?: string;
imageUrl?: string;
areaName?: string;
sigunguName?: string;
regionName?: string;
};

const SNAP_KEY = "st_saved_place_snapshot";

function loadSnapshots(): Record<string, SavedPlaceSnapshot> {
try {
const raw = localStorage.getItem(SNAP_KEY);
if (!raw) return {};
return JSON.parse(raw);
} catch {
return {};
}
}
function saveSnapshots(map: Record<string, SavedPlaceSnapshot>) {
try {
localStorage.setItem(SNAP_KEY, JSON.stringify(map));
} catch {

}
}

export function rememberSavedPlaceSnapshot(s: SavedPlaceSnapshot) {
const map = loadSnapshots();
const key = String(s.contentId);
map[key] = { ...(map[key] || {}), ...s, contentId: key };
saveSnapshots(map);
}

export function forgetSavedPlaceSnapshot(contentId: string | number) {
const map = loadSnapshots();
delete map[String(contentId)];
saveSnapshots(map);
}

export type SavedPlaceRaw = {
cnctrLevel?: number;
entrLevel?: number;
contentId: string | number;
likeCount?: number;
likedCnt?: number;
themeName?: string;
regionName?: string; // 있으면 사용
areaName?: string; // 서버가 주면 사용
sigunguName?: string; // 서버가 주면 사용
imageUrl?: string; // 서버가 주면 사용
firstImage?: string; // 서버가 주면 사용
title?: string; // 서버가 주면 사용
name?: string; // 서버가 주면 사용
placeTitle?: string; // 서버가 주면 사용
savedAt: string | number[];
};

export type SavedPlacesPageRaw = {
content: SavedPlaceRaw[];
page: number;
size: number;
totalElements: number;
totalPages: number;
first: boolean;
last: boolean;
hasNext?: boolean;
hasPrevious?: boolean;
};

export type SavedPlace = {
contentId: string | number;
themeName?: string;
regionName?: string;
entrLevel?: number;
likedCnt?: number;
savedAt: string;
contentId: string | number;
title?: string;
imageUrl?: string;
areaName?: string;
sigunguName?: string;
themeName?: string;
regionName?: string;
entrLevel?: number;
likedCnt?: number;
likeCount?: number;
cnctrLevel?: number;
savedAt: string;
};

export type SavedPlacesPage = {
Expand All @@ -23,12 +99,103 @@ export type SavedPlacesPage = {
hasPrevious?: boolean;
};

function toIsoFromArray(a: any): string {
if (!Array.isArray(a) || a.length < 3) return new Date().toISOString();
const [y, m, d, hh = 0, mm = 0, ss = 0, ns = 0] = a;
const ms = Math.floor((Number(ns) || 0) / 1e6);
return new Date(y, (m ?? 1) - 1, d ?? 1, hh, mm, ss, ms).toISOString();
}

function unwrap<T>(raw: T | ApiResponse<T>): T {
const any = raw as any;
return any && typeof any === "object" && "data" in any ? (any.data as T) : (raw as T);
}

function normalizeOne(raw: SavedPlaceRaw, snap?: SavedPlaceSnapshot): SavedPlace {
const title =
raw.title ??
raw.name ??
raw.placeTitle ??
snap?.title;

const imageUrl =
raw.imageUrl ??
raw.firstImage ??
snap?.imageUrl;

const areaName = raw.areaName ?? snap?.areaName;
const sigunguName = raw.sigunguName ?? snap?.sigunguName;

const entrLevel =
typeof raw.entrLevel === "number"
? raw.entrLevel
: typeof raw.cnctrLevel === "number"
? raw.cnctrLevel
: undefined;

const likedCnt =
typeof raw.likedCnt === "number"
? raw.likedCnt
: typeof raw.likeCount === "number"
? raw.likeCount
: undefined;

const regionName =
raw.regionName ??
snap?.regionName ??
(areaName || sigunguName ? [areaName, sigunguName].filter(Boolean).join(" ") : undefined);

let savedAt: string;
if (Array.isArray(raw.savedAt)) savedAt = toIsoFromArray(raw.savedAt);
else if (typeof raw.savedAt === "string") savedAt = new Date(raw.savedAt).toISOString();
else savedAt = new Date().toISOString();

return {
contentId: raw.contentId,
title,
imageUrl,
areaName,
sigunguName,
themeName: raw.themeName,
regionName,
entrLevel,
likedCnt,
likeCount: raw.likeCount,
cnctrLevel: raw.cnctrLevel,
savedAt,
};
}

function normalizePage(raw: SavedPlacesPageRaw): SavedPlacesPage {
const snaps = loadSnapshots();
const content = (raw.content || []).map((r) =>
normalizeOne(r, snaps[String(r.contentId)])
);
return {
content,
page: Number(raw.page ?? 0),
size: Number(raw.size ?? content.length),
totalElements: Number(raw.totalElements ?? content.length),
totalPages: Number(raw.totalPages ?? 1),
first: Boolean(raw.first ?? true),
last: Boolean(raw.last ?? true),
hasNext: raw.hasNext,
hasPrevious: raw.hasPrevious,
};
}
export async function getSavedPlaces(
{ page = 0, size = 20 }: { page?: number; size?: number } = {}
): Promise<SavedPlacesPage> {
const { data } = await api.get<ApiResponse<SavedPlacesPage>>(
const { data } = await api.get<ApiResponse<SavedPlacesPageRaw> | SavedPlacesPageRaw>(
"/my/places",
{ params: { page, size } }
);
return data.data;
const payload = unwrap<SavedPlacesPageRaw>(data);
return normalizePage(payload);
}

export default {
getSavedPlaces,
rememberSavedPlaceSnapshot,
forgetSavedPlaceSnapshot,
};
34 changes: 0 additions & 34 deletions src/api/Myplace/saveg.ts

This file was deleted.

Loading
Loading