투표 게시글 UI - 2023년 12월 8일 오후 2:00 (GMT+9)

Preview

투표게시글.gif

// 투표 게시글 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}
        />
      )}
    </>
  );
};

투표 후 진행률 - 2023년 12월 11일 오전 10:00 (GMT+9)

투표 항목 선택 후 투표하기 버튼을 클릭하면 선택한 항목에 대해 서버와 통신 후 투표한 개수에 따라 비율을 차트 느낌으로 나타내야한다.

그래서 생각한 방법은 하나의 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 다음에 위치시켜주면 자연스럽게 색상 위로 나오게 된다.

Untitled

투표 결과 확인 UI - 2023년 12월 11일 오후 2:00 (GMT+9)

SvgIcon component화

https://ricale.kr/blog/posts/220409-react-native-svg-icon-component/