diff --git a/src/apis/cards.js b/src/apis/cards.js
index 64aa786..93fce5b 100644
--- a/src/apis/cards.js
+++ b/src/apis/cards.js
@@ -7,6 +7,13 @@ export const getCards = async () => {
return response;
};
+// 단건 명함 조회
+export const getCardDetail = async ({ card_id }) => {
+ const response = await authAxios.get(`/cards/${card_id}`);
+ console.log(response.data.result);
+ return response;
+};
+
// 명함 생성
export const postCards = async ({ data }) => {
const response = await authAxios.post(`/cards`, data);
diff --git a/src/apis/index.js b/src/apis/index.js
index 4b48e61..827152d 100644
--- a/src/apis/index.js
+++ b/src/apis/index.js
@@ -1 +1,3 @@
export { getGroupList, postGroup, deleteGroup, putGroup } from './group';
+export { getCards, getCardDetail, postCards, deleteCards, putCards, searchCards } from './cards';
+export { getMyCard, postMyCard, deleteMyCard, putMyCard } from './myCard';
diff --git a/src/axios/index.js b/src/axios/index.js
index f675705..b19abb7 100644
--- a/src/axios/index.js
+++ b/src/axios/index.js
@@ -6,7 +6,7 @@ export const authAxios = axios.create({
baseURL: VITE_SERVER_DOMAIN,
withCredentials: true,
headers: {
- 'Content-Type': 'application/json',
+ 'Content-Type': 'multipart/form-data',
},
});
diff --git a/src/components/CardInfo/CardInfo.jsx b/src/components/CardInfo/CardInfo.jsx
index 4b1debb..c566fb9 100644
--- a/src/components/CardInfo/CardInfo.jsx
+++ b/src/components/CardInfo/CardInfo.jsx
@@ -5,9 +5,10 @@ import Icon from '../../components/Icon/Icon';
import { Link } from 'react-router-dom';
export default function CardInfo({
+ id,
name,
- role,
- company,
+ position,
+ department,
imageUrl,
isDeleteMode = false,
isSelected = false,
@@ -30,7 +31,7 @@ export default function CardInfo({
{name}
- {role} / {company}
+ {position} / {department}
@@ -50,14 +51,15 @@ export default function CardInfo({
return isDeleteMode ? (
cardContent
) : (
- {cardContent}
+ {cardContent}
);
}
CardInfo.propTypes = {
name: PropTypes.string.isRequired,
- role: PropTypes.string.isRequired,
company: PropTypes.string.isRequired,
+ position: PropTypes.string.isRequired,
+ department: PropTypes.string.isRequired,
imageUrl: PropTypes.string,
isDeleteMode: PropTypes.bool,
isSelected: PropTypes.bool,
diff --git a/src/hooks/useFormData.js b/src/hooks/useFormData.js
new file mode 100644
index 0000000..d7cbef7
--- /dev/null
+++ b/src/hooks/useFormData.js
@@ -0,0 +1,18 @@
+import { useCallback } from 'react';
+
+function useFormData(data) {
+ return useCallback(() => {
+ const formData = new FormData();
+
+ // 객체의 모든 키-값 쌍을 FormData에 추가
+ for (let key in data) {
+ if (data.hasOwnProperty(key) && data[key]) {
+ formData.append(key, data[key]);
+ }
+ }
+
+ return formData;
+ }, [data]);
+}
+
+export default useFormData;
diff --git a/src/pages/CardDetailPage/CardDetailPage.jsx b/src/pages/CardDetailPage/CardDetailPage.jsx
index 9697404..9a264de 100644
--- a/src/pages/CardDetailPage/CardDetailPage.jsx
+++ b/src/pages/CardDetailPage/CardDetailPage.jsx
@@ -1,14 +1,29 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import * as S from './CardDetailPage.style';
import Icon from '../../components/Icon/Icon';
import { DetailBadge } from '../../components';
import ProfileImgDefault from '../../assets/images/profile-img-default.svg';
import CARDS_SAMPLE_DATA from '../../constants/cardsSampleData';
+import { getCardDetail } from '../../apis/cards';
+import { useQuery } from '@tanstack/react-query';
export default function CardDetailPage() {
+ const [info, setInfo] = useState({});
+ console.log(info);
const { id } = useParams();
+ const { data: inputData } = useQuery({
+ queryKey: ['cardDetail', id],
+ queryFn: () => getCardDetail({ card_id: id }),
+ });
+
+ useEffect(() => {
+ if (inputData) {
+ setInfo(inputData.data);
+ }
+ }, [inputData]);
+
const badges = [
{ label: '비즈니스', value: '비즈니스' },
{ label: '방송사', value: '방송사' },
@@ -24,22 +39,22 @@ export default function CardDetailPage() {
? badges.filter((badge) => badge.value === filteredData.category)
: [];
- const data = filteredData || {
- imageUrl: '',
- name: '이름없음',
- job: '직책없음',
- team: '팀없음',
- company: '회사없음',
- phone: '전화번호없음',
- email: '이메일없음',
- tel: '유선전화없음',
- address: '주소없음',
- memo: '메모없음',
- pic1: '사진없음',
- pic2: '사진없음',
- };
+ // const data = filteredData || {
+ // imageUrl: '',
+ // name: '이름없음',
+ // job: '직책없음',
+ // team: '팀없음',
+ // company: '회사없음',
+ // phone: '전화번호없음',
+ // email: '이메일없음',
+ // tel: '유선전화없음',
+ // address: '주소없음',
+ // memo: '메모없음',
+ // pic1: '사진없음',
+ // pic2: '사진없음',
+ // };
- const profileImageUrl = data.imageUrl || ProfileImgDefault;
+ const profileImageUrl = info.profImgUrl || ProfileImgDefault;
const activeBadge = filteredData ? filteredData.category : '';
@@ -69,16 +84,16 @@ export default function CardDetailPage() {
-
+
- {data.name}
+ {info.name}
- {data.job} / {data.team}
+ {info.position} / {info.department}
- {data.company}
+ {info.company}
@@ -90,7 +105,7 @@ export default function CardDetailPage() {
휴대폰
- {data.phone}
+ {info.phone}
@@ -100,40 +115,40 @@ export default function CardDetailPage() {
이메일
- {data.email}
+ {info.email}
유선전화
- {data.tel}
+ {info.tel}
주소
- {data.address}
+ {info.address}
메모
- {data.memo}
+ {info.memo}
그룹
- {(data.pic1 || data.pic2) && (
+ {(info.pic1 || info.pic2) && (
- handleImageClick(data.pic1)}>
-
+ handleImageClick(info.pic1)}>
+
- {data.pic2 ? (
- handleImageClick(data.pic2)}>
-
+ {info.pic2 ? (
+ handleImageClick(info.pic2)}>
+
) : (
diff --git a/src/pages/DetailEditPage/DetailEditPage.jsx b/src/pages/DetailEditPage/DetailEditPage.jsx
index 058ba8b..78899a8 100644
--- a/src/pages/DetailEditPage/DetailEditPage.jsx
+++ b/src/pages/DetailEditPage/DetailEditPage.jsx
@@ -3,10 +3,10 @@ import { useParams, Link, useNavigate } from 'react-router-dom';
import * as S from './DetailEditPage.style';
import Icon from '../../components/Icon/Icon';
import { BlueBadge, AddGroupModal } from '../../components';
-import CARDS_SAMPLE_DATA from '../../constants/cardsSampleData';
import ProfileImgDefault from '../../assets/images/profile-img-default.svg';
-import { getGroupList } from '../../apis';
+import { getGroupList, getCardDetail, putCards } from '../../apis';
import { useQuery } from '@tanstack/react-query';
+import useFormData from '../../hooks/useFormData';
const InputWrapper = memo(
({
@@ -47,241 +47,155 @@ const InputWrapper = memo(
export default function DetailEditPage() {
const { id } = useParams();
- const [activeBadge, setActiveBadge] = useState(null);
-
- const {
- data: groupListData,
- isLoading,
- isError,
- } = useQuery({
- queryKey: ['groupList'],
- queryFn: () => getGroupList(),
- // enabled: !!member_id,
- });
+ const navigate = useNavigate();
+ const [activeBadge, setActiveBadge] = useState(null);
const [badges, setBadges] = useState([]);
-
- useEffect(() => {
- if (groupListData) {
- console.log('groupListData: ', groupListData.data);
- const initialBadges = groupListData.data.map((group) => ({
- label: group.id, // or whatever label makes sense
- value: group.name,
- }));
- setBadges(initialBadges); // Set badges to the fetched groups
- }
- }, [groupListData]);
-
- const filteredData = CARDS_SAMPLE_DATA.find(
- (data) => data.name === decodeURIComponent(id)
- );
-
- // const [filteredBadges, setFilteredBadges] = useState(() => {
- // return filteredData ? groupListData : [];
- // });
-
- const data = filteredData || {
- imageUrl: '',
- };
-
const [profileImage, setProfileImage] = useState(null);
-
+ const [modalVisible, setModalVisible] = useState(false);
const [selectedImage, setSelectedImage] = useState({
pic1: null,
pic2: null,
});
+ const [info, setInfo] = useState({});
const profileImageInputRef = useRef(null);
const profileImageInputRef1 = useRef(null);
const profileImageInputRef2 = useRef(null);
- const onUploadImage = (e) => {
- const files = Array.from(e.target.files || e.dataTransfer.files);
- setSelectedImage(files);
- };
-
- const onUploadProfileImage = (e) => {
- const file = e.target.files[0];
- if (file) {
- setProfileImage(URL.createObjectURL(file));
- }
- };
-
- const handleProfileImageClick = () => {
- profileImageInputRef.current.click();
- };
-
- const handleFirstImageClick = () => {
- profileImageInputRef1.current.click();
- };
-
- const handleSecondImageClick = () => {
- profileImageInputRef2.current.click();
- };
-
- const handleImageUpload = (e, target) => {
- const file = e.target.files[0];
- if (file) {
- const newImageUrl = URL.createObjectURL(file); // 업로드된 파일의 URL 생성
-
- setSelectedImage((prev) => ({
- ...prev,
- [target]: newImageUrl, // target(pic1, pic2)에 따라 상태 업데이트
- }));
- }
- };
-
- const [myInfo, setMyInfo] = useState({
- name: filteredData.name,
- job: filteredData.job,
- company: filteredData.company,
- team: filteredData.team,
- });
-
- const [myContact, setMyContact] = useState({
- phone: filteredData.phone,
- email: filteredData.email,
- tel: filteredData.tel,
- address: filteredData.address,
- memo: filteredData.memo,
- });
-
const handleInfoChange = (e) => {
const { name, value } = e.target;
- setMyInfo((prevInfo) => ({
+ setInfo((prevInfo) => ({
...prevInfo,
[name]: value,
}));
};
- const handleContactChange = (e) => {
- const { name, value } = e.target;
- setMyContact((prevContact) => ({
- ...prevContact,
- [name]: value,
- }));
- };
-
+ // 입력할 데이터
const InputData = [
{
label: '회사명',
type: 'text',
name: 'company',
- value: myInfo.company,
+ value: info.company,
onChange: handleInfoChange,
},
{
label: '직책',
type: 'text',
- name: 'job',
- value: myInfo.job,
+ name: 'position',
+ value: info.position,
onChange: handleInfoChange,
},
{
label: '부서',
type: 'text',
- name: 'team',
- value: myInfo.team,
+ name: 'department',
+ value: info.department,
onChange: handleInfoChange,
},
{
label: '휴대폰',
type: 'tel',
name: 'phone',
- value: myContact.phone,
- onChange: handleContactChange,
+ value: info.phone,
+ onChange: handleInfoChange,
},
{
label: '이메일',
type: 'email',
name: 'email',
- value: myContact.email,
- onChange: handleContactChange,
+ value: info.email,
+ onChange: handleInfoChange,
},
{
label: '유선전화',
type: 'tel',
name: 'tel',
- value: myContact.tel,
- onChange: handleContactChange,
+ value: info.tel,
+ onChange: handleInfoChange,
},
{
label: '주소',
type: 'text',
name: 'address',
- value: myContact.address,
- onChange: handleContactChange,
+ value: info.address,
+ onChange: handleInfoChange,
},
{
label: '메모',
type: 'text',
name: 'memo',
- value: myContact.memo,
- onChange: handleContactChange,
+ value: info.memo,
+ onChange: handleInfoChange,
},
];
- const handleEditClick = (field) => {
- setIsEditing((prev) => ({
- ...prev,
- [field]: true,
- }));
- };
-
- const handleBlur = (field) => {
- setIsEditing((prev) => ({
- ...prev,
- [field]: false,
- }));
- };
+ // 카드 상세 데이터
+ const { data: inputData } = useQuery({
+ queryKey: ['cardDetail', id],
+ queryFn: () => getCardDetail({ card_id: id }),
+ });
- const handleFocus = (field) => {
- setIsEditing((prev) => ({
- ...prev,
- [field]: true,
- }));
- };
+ const profileImageUrl = profileImage || ProfileImgDefault;
- const [isEditing, setIsEditing] = useState({
- name: false,
- job: false,
- company: false,
- phone: false,
- email: false,
- tel: false,
- address: false,
+ const { data: groupListData } = useQuery({
+ queryKey: ['groupList'],
+ queryFn: () => getGroupList(),
});
- const navigate = useNavigate();
+ useEffect(() => {
+ if (groupListData) {
+ const initialBadges = groupListData.data.map((group) => ({
+ label: group.id,
+ value: group.name,
+ }));
+ setBadges(initialBadges);
+ }
+ }, [groupListData]);
- const handleEditComplete = () => {
- const updatedData = {
- ...myInfo,
- ...myContact,
- group: activeBadge,
- };
+ useEffect(() => {
+ if (inputData) {
+ setInfo(inputData.data); // inputData로부터 info 상태 업데이트
+ }
+ }, [inputData]);
- console.log('Data saved successfully:', updatedData);
- setIsEditing({
- name: false,
- job: false,
- company: false,
- phone: false,
- email: false,
- tel: false,
- address: false,
- });
- navigate(`/card/${id}`);
+ const onUploadProfileImage = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setProfileImage(URL.createObjectURL(file));
+ }
};
- const profileImageUrl = profileImage || data.imageUrl || ProfileImgDefault;
-
- const [modalVisible, setModalVisible] = useState(false);
+ const handleProfileImageClick = () => {
+ profileImageInputRef.current.click();
+ };
- const openModal = () => {
- setModalVisible(true);
+ const handleImageUpload = (e, target) => {
+ const file = e.target.files[0];
+ if (file) {
+ const newImageUrl = URL.createObjectURL(file); // 업로드된 파일의 URL 생성
+ setSelectedImage((prev) => ({
+ ...prev,
+ [target]: newImageUrl, // target(pic1, pic2)에 따라 상태 업데이트
+ }));
+ }
};
+ // useFormData 훅을 컴포넌트 내에서 호출
+ const updatedDataForm = useFormData({
+ ...info,
+ group: activeBadge,
+ });
+
+ const handleEditComplete = async () => {
+ try {
+ await putCards({ card_id: id, data: updatedDataForm() }); // updatedDataForm() 호출
+ navigate(`/card/${id}`); // 편집 완료 후 해당 카드 페이지로 리디렉션
+ } catch (error) {
+ console.error('데이터를 저장하는 중에 오류가 발생하였습니다.:', error);
+ }
+ };
return (
<>
@@ -326,30 +240,14 @@ export default function DetailEditPage() {
- {isEditing.name ? (
- handleBlur('name')}
- onFocus={() => handleFocus('name')}
- autoFocus
- />
- ) : (
- <>
- {myInfo.name}
- handleEditClick('name')}>
-
-
- >
- )}
+ {info.name}
사진 아이콘을 클릭하여 명함에 들어갈 프로필 사진을 수정하세요
+
{InputData.map((field, index) => (
@@ -358,16 +256,13 @@ export default function DetailEditPage() {
label={field.label}
type={field.type}
name={field.name}
- value={isEditing[field.name] ? field.value : ''}
- placeholder={isEditing[field.name] ? '' : field.value}
+ value={field.value}
onChange={field.onChange}
- onBlur={() => handleBlur(field.name)}
- onFocus={() => handleFocus(field.name)}
- autoFocus={isEditing[field.name]}
/>
))}
+
그룹
@@ -376,63 +271,53 @@ export default function DetailEditPage() {
activeBadge={activeBadge}
setActiveBadge={setActiveBadge}
/>
-
- 그룹 수정
-
-
-
-
- {(data.pic1 || data.pic2) && (
-
-
-
-
-
- handleImageUpload(e, 'pic1')}
- />
-
-
- {data.pic2 ? (
-
-
+ {/* 이미지 업로드 */}
+
+
+
+
+ profileImageInputRef1.current.click()}
+ />
+ handleImageUpload(e, 'pic1')}
+ />
+
+
-
-
- handleImageUpload(e, 'pic2')}
- />
-
-
- ) : (
-
- )}
-
- )}
+
+
+
+ profileImageInputRef2.current.click()}
+ />
+ handleImageUpload(e, 'pic2')}
+ />
+
+
+
+
(
))}
diff --git a/src/pages/ViewCardPage/ViewCardPage.jsx b/src/pages/ViewCardPage/ViewCardPage.jsx
index b0b1f9e..fb826a2 100644
--- a/src/pages/ViewCardPage/ViewCardPage.jsx
+++ b/src/pages/ViewCardPage/ViewCardPage.jsx
@@ -1,14 +1,15 @@
import * as S from './ViewCardPage.style';
import { Header, SearchBar, BlueBadge, CardInfo } from '../../components';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import Icon from '../../components/Icon/Icon';
-import CARDS_SAMPLE_DATA from '../../constants/cardsSampleData';
+import { getCards } from '../../apis/cards';
export default function ViewCardPage() {
const [activeBadge, setActiveBadge] = useState('전체 보기');
const [isEditCompleteVisible, setIsEditCompleteVisible] = useState(false);
const [isDeleteMode, setIsDeleteMode] = useState(false);
const [selectedCards, setSelectedCards] = useState([]);
+ const [cardsData, setCardsData] = useState([]);
const badges = [
{ label: '전체 보기', value: '전체 보기' },
{ label: '비즈니스', value: '비즈니스' },
@@ -17,10 +18,23 @@ export default function ViewCardPage() {
{ label: '대학교', value: '대학교' },
];
+ async function fetchCards() {
+ try {
+ const response = await getCards();
+ setCardsData(response.data.cards);
+ } catch (error) {
+ console.error('카드 리스트를 불러오지 못했습니다.', error);
+ }
+ }
+
+ useEffect(() => {
+ fetchCards();
+ }, []);
+
let filteredData =
activeBadge === '전체 보기'
- ? CARDS_SAMPLE_DATA
- : CARDS_SAMPLE_DATA.filter((data) => data.category === activeBadge);
+ ? cardsData
+ : cardsData.filter((data) => data.category === activeBadge);
// 이름을 기준으로 오름차순 정렬
filteredData = filteredData.sort((a, b) => a.name.localeCompare(b.name));
@@ -81,9 +95,10 @@ export default function ViewCardPage() {
{filteredData.map((data, index) => (