Skip to content

Commit f9d2c7d

Browse files
committed
chore: 원격 변경사항 추가
1 parent 6385167 commit f9d2c7d

File tree

9 files changed

+568
-0
lines changed

9 files changed

+568
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { create } from 'zustand';
2+
import { responsesData } from '../../../shared/types/responseType';
3+
4+
interface ResponseState {
5+
response: responsesData[];
6+
selectedField: string;
7+
selectedResponse: responsesData[];
8+
currentIndex: number;
9+
10+
setResponses: (responses: responsesData[]) => void;
11+
setSelectedField: (field: string) => void;
12+
setSelectedResponse: (responseName: string, responseEmail: string) => void;
13+
setCurrentIndex: (updateFn: (prevIndex: number) => number) => void;
14+
}
15+
16+
export const useResponseStore = create<ResponseState>((set) => ({
17+
response: [],
18+
selectedField: '',
19+
selectedResponse: [],
20+
currentIndex: 0,
21+
22+
setResponses: (response) => {
23+
set(() => ({
24+
response: response,
25+
selectedField: response.length > 0 ? Object.keys(response[0])[1] : ''
26+
}));
27+
},
28+
29+
setSelectedField: (field) => {
30+
set({ selectedField: field });
31+
},
32+
33+
setSelectedResponse: (responseName, responseEmail) => {
34+
set((state) => {
35+
const filteredResponses = state.response.filter((res) => res.name === responseName && res.email === responseEmail);
36+
return {
37+
selectedResponse: filteredResponses,
38+
currentIndex: 0,
39+
};
40+
});
41+
},
42+
43+
setCurrentIndex: (updateFn) => set((state) => ({
44+
currentIndex: updateFn(state.currentIndex),
45+
})),
46+
47+
}));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { create } from "zustand";
2+
3+
export interface TOption {
4+
type: string;
5+
optionName: string;
6+
required: boolean;
7+
choices: string[];
8+
}
9+
10+
interface TicketOptionState {
11+
currentPage: number;
12+
setCurrentPage: (page: number) => void;
13+
14+
selectedOptions: { [index: number]: { [key: string]: string | string[] } };
15+
setOption: (index: number, optionName: string, value: string | string[]) => void;
16+
}
17+
18+
export const useTicketOptionStore = create<TicketOptionState>((set) => ({
19+
currentPage: 1,
20+
setCurrentPage: (page: number) => set({ currentPage: page }),
21+
22+
selectedOptions: {},
23+
setOption: (index, optionName, value) => {
24+
set((state) => {
25+
const updatedSelectedOptions = { ...state.selectedOptions };
26+
if (!updatedSelectedOptions[index]) {
27+
updatedSelectedOptions[index] = {};
28+
}
29+
updatedSelectedOptions[index][optionName] = value;
30+
return { selectedOptions: updatedSelectedOptions };
31+
});
32+
},
33+
}));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Checkbox from "../../../../design-system/ui/Checkbox";
2+
import { Option } from "../../../shared/types/responseType";
3+
4+
interface Response {
5+
id: string;
6+
name: string;
7+
selectedOptions: {
8+
[key: string]: string;
9+
};
10+
}
11+
12+
interface OptionSectionProps {
13+
responses: Response[];
14+
options: Option[];
15+
}
16+
17+
const OptionSection = ({ responses, options }: OptionSectionProps) => {
18+
return (
19+
<div>
20+
{responses.map((response) => (
21+
<div key={response.id}>
22+
{options.map((option) => {
23+
const selectedChoice = response.selectedOptions[option.optionName];
24+
25+
return (
26+
<div key={option.optionName}>
27+
<p className="block mb-2 text-sm md:text-lg">{option.optionName}</p>
28+
<ul className="space-y-1">
29+
{option.choices.map((choice: string) => (
30+
<li key={choice}>
31+
<Checkbox
32+
label={choice}
33+
checked={selectedChoice === choice}
34+
onChange={() => {}}
35+
disabled={selectedChoice !== choice}
36+
/>
37+
</li>
38+
))}
39+
</ul>
40+
</div>
41+
);
42+
})}
43+
</div>
44+
))}
45+
</div>
46+
);
47+
};
48+
49+
export default OptionSection;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import IconButton from '../../../../design-system/ui/buttons/IconButton';
2+
import { responsesData } from '../../../shared/types/responseType';
3+
import DropDown from '../../../shared/ui/DropDown';
4+
import { createFieldMappings } from '../../lib/createFieldMappings';
5+
import rightButton from '../../../../public/assets/main/RightButton.svg';
6+
import leftButton from '../../../../public/assets/main/LeftButton.svg';
7+
8+
interface ResponseFilterProps {
9+
responses: responsesData[];
10+
listType: 'summary' | 'query' | 'individual';
11+
selectedField: { v1: string; v2: string };
12+
setSelectedField: (v1: string, v2: string) => void;
13+
setCurrentIndex: (updateFn: (prevIndex: number) => number) => void;
14+
currentIndex: number;
15+
responsesLength: number;
16+
options: { v1: string; v2: string }[];
17+
}
18+
19+
const ResponseFilter = ({
20+
responses,
21+
listType,
22+
selectedField,
23+
setSelectedField,
24+
setCurrentIndex,
25+
currentIndex,
26+
responsesLength,
27+
options
28+
}: ResponseFilterProps) => {
29+
const { fieldMapToKorean } = createFieldMappings(responses);
30+
const optionsToKorean = options.map(option => ({
31+
v1: fieldMapToKorean[option.v1] || option.v1,
32+
v2: option.v2
33+
}));
34+
const handlePageChange = (direction: 'prev' | 'next') => {
35+
const nextIndex = direction === 'prev' ? Math.max(currentIndex - 1, 0) : Math.min(currentIndex + 1, responsesLength - 1);
36+
setCurrentIndex(() => nextIndex);
37+
38+
const nextOption = options[nextIndex];
39+
40+
if (nextOption) {
41+
setSelectedField(nextOption.v1, nextOption.v2);
42+
}
43+
};
44+
return (
45+
<div className="bg-white p-4 flex flex-col gap-2 mb-4">
46+
<div className="flex justify-between items-center">
47+
<div className="w-2/3">
48+
<DropDown
49+
options={optionsToKorean}
50+
selectedValue={selectedField.v1}
51+
onSelect={(selectedName, selectedEmail) => {
52+
const selectedOption = options.find(opt =>
53+
(listType === 'query' ? fieldMapToKorean[opt.v1] === selectedName : opt.v1 === selectedName) &&
54+
opt.v2 === selectedEmail
55+
);
56+
if (selectedOption) {
57+
setSelectedField(selectedOption.v1, selectedOption.v2);
58+
setCurrentIndex(() => 0);
59+
}
60+
}}
61+
/>
62+
</div>
63+
<div className="flex items-center gap-1 md:gap-2 ml-auto ">
64+
<IconButton
65+
iconPath={<img src={leftButton} alt="왼쪽 버튼" />}
66+
onClick={() => {
67+
if (listType === 'individual') {
68+
setCurrentIndex((prev) => Math.max(prev - 1, 0));
69+
} else {
70+
handlePageChange('prev');
71+
}
72+
}}
73+
/>
74+
<span className='text-sm md:text-base w-10 text-center'>{Math.floor(currentIndex / 1) + 1} / {Math.ceil(responsesLength / 1)}</span>
75+
<IconButton
76+
iconPath={<img src={rightButton} alt="오른쪽 버튼" />}
77+
onClick={() => {
78+
if (listType === 'individual') {
79+
setCurrentIndex((prev) => Math.min(prev + 1, responsesLength - 1));
80+
} else {
81+
handlePageChange('next');
82+
}
83+
}}
84+
/>
85+
</div>
86+
</div>
87+
</div>
88+
);
89+
};
90+
91+
export default ResponseFilter;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useResponseStore } from '../model/ResponseStore';
2+
import { responsesInfo } from '../../../shared/types/responseType';
3+
import ResponseFilter from './ResponseFilter';
4+
import SelectedResponseList from './SelectedResponseList';
5+
import { createFieldMappings } from '../../lib/createFieldMappings';
6+
import { useEffect } from 'react';
7+
8+
interface ResponsesListProps {
9+
listType: 'summary' | 'query' | 'individual';
10+
}
11+
12+
const ResponsesList = ({ listType }: ResponsesListProps) => {
13+
const { response, selectedField, setSelectedField, selectedResponse, setSelectedResponse, currentIndex, setCurrentIndex } = useResponseStore();
14+
const { fieldMap, fieldMapToKorean } = createFieldMappings(response);
15+
const queryOptions = response && response[0]
16+
? Object.keys(response[0])
17+
.filter(key => key !== 'id' && key !== 'selectedOptions')
18+
.map(key => ({
19+
v1: fieldMap[key] || key,
20+
v2: ""
21+
}))
22+
: [];
23+
24+
useEffect(() => {
25+
setCurrentIndex(() => 0);
26+
}, [listType, setCurrentIndex]);
27+
28+
const renderSection = (title: string, key: keyof typeof responsesInfo[0], isSummaryPage: boolean) => {
29+
const transTitle = fieldMapToKorean[title];
30+
31+
return (
32+
<div className="bg-white p-4 flex flex-col gap-2 mb-4">
33+
<div className="flex justify-between items-center text-xs bg-white px-2 md:px-3 py-3">
34+
<p className='text-base font-bold'>{transTitle}</p>
35+
<p>응답 {response.length}</p>
36+
</div>
37+
38+
{response.length === 0 ? (
39+
<p>응답이 없습니다.</p>
40+
) : (
41+
<div className={isSummaryPage ? "h-full max-h-48 overflow-y-auto space-y-2" : "h-full overflow-y-auto space-y-2"}>
42+
{response.map((response) => (
43+
<div className="flex justify-between text-xs bg-gray-100 shadow-sm px-2 md:px-3 py-3 gap-2" key={response.id}>
44+
<p>{typeof response[key] === 'object' ? JSON.stringify(response[key]) : response[key]}</p>
45+
</div>
46+
))}
47+
</div>
48+
)}
49+
</div>
50+
);
51+
};
52+
53+
const renderList = () => {
54+
switch (listType) {
55+
case 'summary':
56+
return (
57+
<>
58+
{renderSection('name', 'name', true)}
59+
{renderSection('phone', 'phone', true)}
60+
{renderSection('email', 'email', true)}
61+
{renderSection('grade', 'grade', true)}
62+
{renderSection('email', 'email', true)}
63+
</>
64+
);
65+
case 'query':
66+
return (
67+
<>
68+
<ResponseFilter
69+
responses={response}
70+
listType={listType}
71+
selectedField={{ v1: fieldMapToKorean[selectedField], v2: "" }}
72+
setSelectedField={setSelectedField}
73+
setCurrentIndex={setCurrentIndex}
74+
currentIndex={currentIndex}
75+
responsesLength={queryOptions.length}
76+
options={queryOptions}
77+
/>
78+
{renderSection(selectedField, selectedField as keyof typeof responsesInfo[0], false)}
79+
</>
80+
);
81+
case 'individual':
82+
return (
83+
<div>
84+
<ResponseFilter
85+
responses={response}
86+
listType={listType}
87+
selectedField={selectedResponse.length > 0 ?
88+
{ v1: selectedResponse[0].name, v2: selectedResponse[0].email } : { v1: '전체', v2: '' }}
89+
setSelectedField={setSelectedResponse}
90+
setCurrentIndex={setCurrentIndex}
91+
currentIndex={currentIndex}
92+
responsesLength={selectedResponse.length > 0 ? selectedResponse.length : response.length}
93+
options={[{ v1: '전체', v2: '' },
94+
...response.map((res) => ({ v1: res.name, v2: res.email }))]}
95+
/>
96+
<SelectedResponseList
97+
currentIndex={currentIndex}
98+
/>
99+
</div>
100+
);
101+
default:
102+
return null;
103+
}
104+
};
105+
return (
106+
<div>
107+
{renderList()}
108+
</div>
109+
);
110+
};
111+
112+
export default ResponsesList;

0 commit comments

Comments
 (0)