186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
|
|
import { Transaction } from '@/components/TransactionCard';
|
|
import { CATEGORY_TITLE_SUGGESTIONS } from '@/constants/categoryIcons';
|
|
|
|
// 지출 제목 사용 빈도를 저장하는 로컬 스토리지 키
|
|
const TITLE_PREFERENCES_KEY = 'userTitlePreferences';
|
|
|
|
// 최대 저장 제목 개수 (카테고리별)
|
|
const MAX_TITLES_PER_CATEGORY = 15;
|
|
|
|
// 최소 사용 횟수 (이 횟수 미만이면 삭제 대상)
|
|
const MIN_USAGE_COUNT = 2;
|
|
|
|
// 사용자 제목 선호도 타입 정의
|
|
export interface TitlePreference {
|
|
count: number; // 사용 횟수
|
|
lastUsed: string; // 마지막 사용 일시 (ISO 문자열)
|
|
}
|
|
|
|
// 카테고리별 제목 선호도
|
|
export interface CategoryTitlePreferences {
|
|
[title: string]: TitlePreference;
|
|
}
|
|
|
|
// 전체 제목 선호도 데이터 구조
|
|
export interface UserTitlePreferences {
|
|
음식: CategoryTitlePreferences;
|
|
쇼핑: CategoryTitlePreferences;
|
|
교통: CategoryTitlePreferences;
|
|
기타: CategoryTitlePreferences;
|
|
[key: string]: CategoryTitlePreferences;
|
|
}
|
|
|
|
/**
|
|
* 로컬 스토리지에서 사용자 제목 선호도 데이터 로드
|
|
*/
|
|
export const loadUserTitlePreferences = (): UserTitlePreferences => {
|
|
try {
|
|
const storedPreferences = localStorage.getItem(TITLE_PREFERENCES_KEY);
|
|
if (storedPreferences) {
|
|
return JSON.parse(storedPreferences);
|
|
}
|
|
} catch (error) {
|
|
console.error('제목 선호도 데이터 로드 중 오류:', error);
|
|
}
|
|
|
|
// 기본값 반환 - 기본 카테고리 구조 생성
|
|
return {
|
|
음식: {},
|
|
쇼핑: {},
|
|
교통: {},
|
|
기타: {}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 사용자 제목 선호도 데이터 저장
|
|
*/
|
|
export const saveUserTitlePreferences = (preferences: UserTitlePreferences): void => {
|
|
try {
|
|
localStorage.setItem(TITLE_PREFERENCES_KEY, JSON.stringify(preferences));
|
|
} catch (error) {
|
|
console.error('제목 선호도 데이터 저장 중 오류:', error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 트랜잭션에서 제목 사용 업데이트
|
|
* 새로운 트랜잭션이 추가되거나 수정될 때 호출
|
|
*/
|
|
export const updateTitleUsage = (transaction: Transaction): void => {
|
|
// 타입이 expense가 아니거나 제목이 없으면 무시
|
|
if (transaction.type !== 'expense' || !transaction.title) return;
|
|
|
|
const { category, title } = transaction;
|
|
const preferences = loadUserTitlePreferences();
|
|
|
|
// 해당 카테고리가 없으면 초기화
|
|
if (!preferences[category]) {
|
|
preferences[category] = {};
|
|
}
|
|
|
|
// 해당 제목이 없으면 새로 추가 (새 제목 삽입)
|
|
if (!preferences[category][title]) {
|
|
console.log(`새 제목 추가: "${title}" (${category} 카테고리)`);
|
|
preferences[category][title] = {
|
|
count: 0,
|
|
lastUsed: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
// 카운트 증가 및 마지막 사용 시간 업데이트
|
|
preferences[category][title].count += 1;
|
|
preferences[category][title].lastUsed = new Date().toISOString();
|
|
|
|
// 카테고리별 최대 제목 수 관리 (사용 빈도가 낮은 제목 제거)
|
|
const titles = Object.entries(preferences[category]);
|
|
if (titles.length > MAX_TITLES_PER_CATEGORY) {
|
|
// 사용 횟수 및 최근 사용일 기준으로 정렬
|
|
const sortedTitles = titles.sort((a, b) => {
|
|
// 먼저 사용 횟수로 비교 (내림차순)
|
|
const countDiff = b[1].count - a[1].count;
|
|
if (countDiff !== 0) return countDiff;
|
|
|
|
// 사용 횟수가 같으면 최근 사용일로 비교 (내림차순)
|
|
return new Date(b[1].lastUsed).getTime() - new Date(a[1].lastUsed).getTime();
|
|
});
|
|
|
|
// 정렬 후 하위 항목 제거 (기준: MIN_USAGE_COUNT 미만 & 가장 적게 사용됨)
|
|
const titlesToRemove = sortedTitles
|
|
.slice(MAX_TITLES_PER_CATEGORY)
|
|
.filter(([_, pref]) => pref.count < MIN_USAGE_COUNT)
|
|
.map(([title]) => title);
|
|
|
|
if (titlesToRemove.length > 0) {
|
|
console.log(`사용 빈도가 낮은 제목 제거: ${titlesToRemove.length}개`);
|
|
|
|
// 제거할 제목들을 선호도에서 삭제
|
|
titlesToRemove.forEach(title => {
|
|
delete preferences[category][title];
|
|
});
|
|
}
|
|
}
|
|
|
|
// 저장
|
|
saveUserTitlePreferences(preferences);
|
|
};
|
|
|
|
/**
|
|
* 카테고리별 추천 제목 목록 가져오기
|
|
* 사용자 선호도 + 기본 추천 제목 결합
|
|
*/
|
|
export const getPersonalizedTitleSuggestions = (category: string): string[] => {
|
|
// 기본 제목 목록
|
|
const defaultSuggestions = CATEGORY_TITLE_SUGGESTIONS[category] || [];
|
|
|
|
try {
|
|
const preferences = loadUserTitlePreferences();
|
|
const categoryPreferences = preferences[category] || {};
|
|
|
|
// 사용 횟수 기준으로 정렬된 사용자 정의 제목 목록
|
|
const personalizedTitles = Object.entries(categoryPreferences)
|
|
.sort((a, b) => {
|
|
// 우선 사용 횟수로 정렬 (내림차순)
|
|
const countDiff = b[1].count - a[1].count;
|
|
if (countDiff !== 0) return countDiff;
|
|
|
|
// 사용 횟수가 같으면 최근 사용일자로 정렬 (내림차순)
|
|
const dateA = new Date(a[1].lastUsed).getTime();
|
|
const dateB = new Date(b[1].lastUsed).getTime();
|
|
return dateB - dateA;
|
|
})
|
|
.map(([title]) => title);
|
|
|
|
// 사용자 선호 제목에는 없지만 기본 제목에 있는 항목 추가
|
|
const remainingDefaultTitles = defaultSuggestions.filter(
|
|
title => !personalizedTitles.includes(title)
|
|
);
|
|
|
|
// 최종 개인화된 제목 목록 (선호도 순 + 기본 제목)
|
|
return [...personalizedTitles, ...remainingDefaultTitles];
|
|
} catch (error) {
|
|
console.error('개인화된 제목 목록 생성 중 오류:', error);
|
|
return defaultSuggestions;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 트랜잭션 추가 시 제목 사용 빈도 업데이트 및 관리 함수
|
|
* AddTransactionButton에서 호출하기 위한 래퍼 함수
|
|
*/
|
|
export const manageTitleSuggestions = (transaction: Transaction): void => {
|
|
// 제목 사용 업데이트 (추가 및 카운트 증가)
|
|
updateTitleUsage(transaction);
|
|
|
|
// 개발 모드에서 저장된 제목 선호도 로깅
|
|
if (process.env.NODE_ENV === 'development') {
|
|
const preferences = loadUserTitlePreferences();
|
|
const category = transaction.category;
|
|
if (preferences[category]) {
|
|
const count = Object.keys(preferences[category]).length;
|
|
console.log(`${category} 카테고리 저장된 제목 수: ${count}개`);
|
|
}
|
|
}
|
|
};
|