내가 맡았던 마이페이지 부분에서 청첩장 제작이 얼마까지 완료되었는지 사용작에게 제공해주는 것이 유저가 계속해서 진행하는데에 도움이 될 것 같아 디자인이 변경이 되었다.
원형프로그래스바가 채워지는 형태로 진행을 하고
팀원들과 완성의 depth을 어디기준으로 잡아야 하나라는 이야기를 나누었다.
지금 우리 데이터는 큰 객체 Form안에 갤러리정보, 글꼴 정보, 예식장 정보 등 데이터를 저장하고 있다.
글꼴 정보를 받는 폼 안에서도 글꼴 색상, 폰트선택을 담은 값들이 있지만 depth를 이 깊이까지 잡게 된다면 많이 복잡해질 수 있다고 생각을 하였다. 갤러리정보, 글꼴 정보와 같이 조금 더 큰 단위에서 formdata에 기본값이 들어오지 않는다면 진척이 되고 있는 것으로 판단을 하여 진척도를 나타낼 수 있도록 구현하려고 한다.
ㅁ 사용자가 입력해야하는 필드
formdata 기본값을 가져오고 , supabase에서 청첩장 ID를 비교하여 청첩장 데이터를 가져오는 로직을 먼저 작성했다.
id를 받아 id에 맞게 등록되어있는 데이터를 가져왔다.
export const fetchInvitationFields = async (id: string) => {
const { data } = await browserClient
.from('invitation')
.select('*')
.eq('user_id', id)
.order('created_at', { ascending: false });
return data?.[0];
};
<기존 데이터 default값>
const DEFAULT_VALUE = {
bg_color: { r: 255, g: 255, b: 255, a: 1, name: '흰색' },
personal_info: {
bride: {
name: '',
relation: '',
phoneNumber: '',
father: { name: '', relation: '', phoneNumber: '', isDeceased: false },
mother: { name: '', relation: '', phoneNumber: '', isDeceased: false },
},
groom: {
name: '',
relation: '',
phoneNumber: '',
father: { name: '', relation: '', phoneNumber: '', isDeceased: false },
mother: { name: '', relation: '', phoneNumber: '', isDeceased: false },
},
},
account: {
title: '',
content: '',
bride: [
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
],
groom: [
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
{ bank: '', accountNumber: '', depositor: '', kakaopay: '' },
],
},
guestbook: true,
attendance: false,
wedding_info: {
date: '2024.11.22',
time: { hour: '오전 00', minute: '00' },
weddingHallAddress: '',
weddingHallName: '',
weddingHallContact: '',
},
main_photo_info: {
leftName: '',
rightName: '',
icon: '♥︎',
introduceContent: '',
imageUrl: '',
},
navigation_detail: {
map: false,
navigationButton: false,
subway: '',
bus: '',
},
gallery: {
images: [],
grid: 3,
ratio: 'square',
},
type: 'scroll',
mood_preset: {
mood: 'classic',
preset: {
name: 'preset1',
label: '프리셋 1',
image: '/assets/images/presets/classicPreset1.svg',
},
},
stickers: [],
img_ratio: {
ratio: '',
position: 0,
},
greeting_message: {
title: '',
content: '',
},
d_day: true,
main_view: {
name: '기본',
type: 'default',
},
isPrivate: false,
render_order: extractOrderAndType(),
font_info: {
size: 0,
fontName: 'Main',
color: {
r: 0,
g: 0,
b: 0,
a: 100,
name: '커스텀',
},
},
};
// 깊은 복사를 위한 deepEquals 함수
function deepEquals(target1: unknown, target2: unknown): boolean {
return isEqual(sortedTarget1, sortedTarget2);
}
const calculateProgress = (supabaseData: Record<string, unknown>, defaultValue: Record<string, unknown>): number => {
let completedFields = 0;
const fieldsToCheck = [
// 체크해야하는 필드
'personal_info',
'account',
'wedding_info',
'main_photo_info',
'navigation_detail',
'gallery',
'greeting_message',
'font_info',
];
// 업로드 되어있는 데이터와 기본값 데이터를 반복하여 검사
for (const field of fieldsToCheck) {
const fieldData = supabaseData?.[field];
const defaultFieldData = defaultValue?.[field];
if (deepEquals(fieldData, defaultFieldData)) {
} else {
completedFields += 1;
}
}
const totalFields = fieldsToCheck.length;
const progressPercentage = Math.min(Math.round((completedFields / totalFields) * 100), 100);
return progressPercentage;
};
export const calculateProgressPercentage = async (id: string): Promise<number> => {
const supabaseData = await fetchInvitationFields(id);
if (!supabaseData) {
return 0;
}
return calculateProgress(supabaseData, DEFAULT_VALUE);
};
💥 문제상황
: 이렇게 처리를 해주었음에도 여전히 데이터들을 제대로 비교하지 못하고 있었다. 진척률에 영향을 주는 필드들에 데이터를 넣고 supabase에서 들어오는 데이터와 기본값들을 비교해보았다. supabase와 기본값에서 보이는 객체 key들의 순서가 달랐고 이로 인해 제대로 된 비교가 되지 않았던 것이었다.
👀 고려했던 방법
🌟 해결방법
function deepSort(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(deepSort);
}
if (value && typeof value === 'object' && value.constructor === Object) {
const sortedObj: Record<string, unknown> = {};
Object.keys(value)
.sort()
.forEach((key) => {
sortedObj[key] = deepSort((value as Record<string, unknown>)[key]);
});
return sortedObj;
}
return value;
}
깊은 정렬 후 깊은 비교를 할 수 있도록 수정
function deepEquals(target1: unknown, target2: unknown): boolean {
if (target1 === undefined) {
return false;
}
const sortedTarget1 = deepSort(target1);
const sortedTarget2 = deepSort(target2);
return isEqual(sortedTarget1, sortedTarget2);
}
function deepSort(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(deepSort);
}
if (value && typeof value === 'object' && value.constructor === Object) {
const sortedObj: Record<string, unknown> = {};
Object.keys(value)
.sort()
.forEach((key) => {
sortedObj[key] = deepSort((value as Record<string, unknown>)[key]);
});
return sortedObj;
}
return value;
}
function deepEquals(target1: unknown, target2: unknown): boolean {
if (target1 === undefined) {
return target1 === target2;
}
const sortedTarget1 = deepSort(target1);
const sortedTarget2 = deepSort(target2);
console.log('sortedTarget1', sortedTarget1);
console.log('sortedTarget2', sortedTarget2);
return isEqual(sortedTarget1, sortedTarget2);
}
export const fetchInvitationFields = async (id: string) => {
const { data } = await browserClient.from('invitation').select('*').eq('user_id', id);
return data?.[0];
};
const calculateProgress = (supabaseData: Record<string, unknown>, defaultValue: Record<string, unknown>): number => {
let completedFields = 0;
const fieldsToCheck = [
'personal_info',
'account',
'wedding_info',
'main_photo_info',
'navigation_detail',
'gallery',
'greeting_message',
'font_info',
];
for (const field of fieldsToCheck) {
const fieldData = supabaseData?.[field];
const defaultFieldData = defaultValue?.[field];
defaultFieldData);
if (!deepEquals(fieldData, defaultFieldData)) {
completedFields += 1;
}
}
const totalFields = fieldsToCheck.length;
const progressPercentage = Math.min(Math.round((completedFields / totalFields) * 100), 100);
console.log('Completed Fields:', completedFields);
console.log('Progress Percentage:', progressPercentage);
return progressPercentage;
};
export const calculateProgressPercentage = async (id: string): Promise<number> => {
const supabaseData = await fetchInvitationFields(id);
if (!supabaseData) {
return 0;
}
return calculateProgress(supabaseData, DEFAULT_VALUE);
};
원형 프로그래스바를
conic-gradient를 사용하여 퍼센트에 따라 색상이 채워지도록 설정해주었다.
const gradientColor =
progressPercentage !== null
? `conic-gradient(#ff6666 ${progressPercentage}%, #e0e0e0 0%)`
: 'conic-gradient( #e0e0e0 0%, #FFFFFF 100%)';
<div
className='w-[96px] h-[96px] absolute inset-0 rounded-full flex justify-center items-center overflow-hidden'
style={{
background: gradientColor,
}}
>
<div className='w-[90%] h-[90%] overflow-hidden flex rounded-full'>
<Image
src={invitationCard.main_photo_info?.imageUrl || '/assets/images/defaultImg.png'}
alt='invitationImg'
width={90}
height={90}
className='rounded-full'
/>
</div>
</div>