// 투표 게시글 UI 하드코딩
export default function VotePage({ navigation, route }) {
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Button
style={{ backgroundColor: theme.background }}
text={<KebabMenuIcon />}
/>
),
title: '아차산 산책 모임',
});
}, []);
return (
<ScrollView>
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.badge}>투표</Text>
<Text style={{ fontWeight: 'bold' }}>
모임 일정 투표 부탁드립니다.
</Text>
</View>
<View
style={{
...styles.row,
paddingVertical: 16,
borderBottomColor: voteTheme.grayBorder,
borderBottomWidth: 0.5,
marginBottom: 20,
}}
>
<Image source={require('@/assets/icons/user-thumbnail.png')} />
<View style={{ marginLeft: 10, flex: 1 }}>
<TitleText>친절한 사장님</TitleText>
<View
style={{
...styles.row,
justifyContent: 'space-between',
}}
>
<Text style={styles.grayText}>2023년 12월 1일 09:17</Text>
<Text style={styles.grayText}>조회 251</Text>
</View>
</View>
</View>
<Text style={{ textAlign: 'left' }}>
<Text style={{ color: theme.main }}>투표 </Text>
올해 마지막 정모를 계획중입니다. 다들 가능한 일정에 투표
부탁드립니다.^^
</Text>
{route.params?.vote && (
<VoteForm navigation={navigation} route={route} />
)}
</View>
</ScrollView>
);
}
// 투표 폼
const VoteForm = ({ navigation, route }) => {
const [
isModal,
modalType,
onPressModalOpen,
onPressModalClose,
confirm,
cancel,
] = useModal();
const data = route.params?.vote;
const [type, setType] = useState(
Array.from(Array(data.vote.length), () => false),
);
const [isSelect, setIsSelect] = useState(false);
useEffect(() => {
// console.log(date);
const count = type.filter((v) => v).length;
console.log(data.options, count);
// 투표 항목 1개라도 선택 시 투표하기 활성화
if (count) setIsSelect(true);
else setIsSelect(false);
}, [type]);
const selectCount = () => {
const count = type.filter((v) => v).length + 1;
// 복수 선택 가능 옵션이 true이면 복수 선택 가능하고 아닌데 복수 선택하려면 모달 띄우기
if (count >= 2 && !data.options.multiSelect) {
onPressModalOpen('impossibleMultiSelect', () => onPressModalClose());
return false;
}
return true;
};
const today = new Date();
const date = new Date(JSON.parse(data.endDate)).getDate() - today.getDate();
return (
<>
<View style={styles.voteContainer}>
<View style={styles.voteHeader}>
<View style={styles.text}>
<VoteIcon width={24} height={24} />
<Text style={{ color: voteTheme.optionText, fontSize: 15 }}>
투표{' '}
<Text style={{ color: theme.main, fontSize: 15 }}>
{date}일 남음
</Text>
</Text>
</View>
<View style={styles.text}>
{data.options?.anonymous && (
<Text style={{ color: voteTheme.addItemText, fontSize: 15 }}>
익명 투표{data.options?.multiSelect && 'ㆍ'}
</Text>
)}
{data.options?.multiSelect && (
<Text style={{ color: voteTheme.addItemText, fontSize: 15 }}>
복수 선택 가능
</Text>
)}
</View>
</View>
<View style={{ flex: 1, gap: 8, marginTop: 16 }}>
{data.vote?.map(
(item, index) =>
item.text && (
<View style={styles.voteItem} key={index}>
<VoteCheckBox
onParentState={() => {
setType(
type.map((v, i) =>
i === index && selectCount() ? !type[index] : v,
),
);
}}
open={type[index]}
/>
<Text style={styles.voteItemText}>{item.text}</Text>
</View>
),
)}
<View style={{ flexDirection: 'row', flex: 1 }}>
<Button
style={{
...styles.button,
backgroundColor: isSelect
? voteTheme.voteSubmitBtn
: voteTheme.PreviewModifyAndDeleteBtn,
}}
text={'투표하기'}
color={{
color: isSelect ? theme.main : voteTheme.addItemText,
fontSize: 20,
}}
/>
</View>
</View>
</View>
{isModal && (
<Modal
confirmCallback={confirm}
modalType={modalType}
cancelCallback={cancel}
/>
)}
</>
);
};
투표 항목 선택 후 투표하기 버튼을 클릭하면 선택한 항목에 대해 서버와 통신 후 투표한 개수에 따라 비율을 차트 느낌으로 나타내야한다.
그래서 생각한 방법은 하나의 View에서 View의 넓이가 개수의 비율로 설정해주면 배경색으로 구분되지 않을까?라는 생각으로 시작해봤다.
item.text && (
<View
style={{
backgroundColor: voteTheme.addItemBackground,
borderRadius: 11,
position: 'relative',
}}
key={index}
>
{isVote && (
<View
style={{
backgroundColor: voteTheme.voteSubmitBtn,
position: 'absolute',
width: `${
(VOTER.filter((v) => v.vote === index).length / 10) *
100
}%`,
height: '100%',
borderRadius: 11,
}}
></View>
)}
<View style={styles.voteItem}>
<View
style={{ flexDirection: 'row', alignItems: 'center' }}
>
<VoteCheckBox
onParentState={() => handleClickItem(index)}
open={type[index]}
/>
<Button
onPress={() => handleClickItem(index)}
style={{ backgroundColor: 'transparent' }}
text={
<Text style={styles.voteItemText}>{item.text}</Text>
}
/>
</View>
<Text>{`${
VOTER.filter((v) => v.vote === index).length
}명`}</Text>
</View>
</View>
),
const styles = StyleSheet.create({
voteItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
},
voteItemText: {
textAlign: 'left',
color: voteTheme.PreviewItemText,
},
});
그러기 위해서는 부모의 View에 relative 속성을 주고, 비율 색상을 나타내는 View는 absolute position으로 위치 시키고 넓이는 (VOTER.filter((v) => v.vote === index).length / 10) * 100
비율 계산으로 넣어주었다.
그리고 voteItem에 zindex를 줘서 비율 색상보다 위에 위치하도록 했는데, RN도 Topdown 방식이라 밑으로 갈수록 다른 태그들 보다 위에 있게 된다.
따라서 voteItem View를 비율 색상 View 다음에 위치시켜주면 자연스럽게 색상 위로 나오게 된다.
https://ricale.kr/blog/posts/220409-react-native-svg-icon-component/