React-Native/[프로젝트] 택시 운행관리 기록장

[React-native CLI] Home화면 기능 구현하기

개발조각 2024. 3. 23. 22:16
728x90
반응형

Home화면에서 구현해야 될 기능을 크게 3가지로 구분할 수 있습니다.

  1. 현재 달이면 다음 달 이동 못하게 하기
  2. 월간 운행 정보 데이터 구하기
  3. 연간 영업 금액 구하기

 

구현 화면


 

기존 코드


// react, react-native
import React, {useEffect, useState} from 'react';
import {ScrollView} from 'react-native';

// library
import dayjs from 'dayjs';

// assets, utils, realm
import {useRecoilState} from 'recoil';
import {recordState} from '../recoil/atoms.ts';
import {readAllRecord} from '../realm/recordRealmFunctions.ts';

// component
import YearBusinessAmount from '../components/home/YearBusinessAmount.tsx';
import MonthCalendar from '../components/home/MonthCalendar.tsx';
import MonthBusinessAmount from '../components/home/MonthBusinessAmount.tsx';
import MonthRecordInfo from '../components/home/MonthRecordInfo.tsx';
import CreateButton from '../components/home/CreateButton.tsx';

// style
import {Home as Style} from '../styles/home.styles.ts';

const Home = () => {
  const currentMonth = dayjs().format('YYYY-MM'); // 현재 달: 년-월
  const [selectMonth, setSelectMonth] = useState(currentMonth);
  const [recordData, setRecordData] = useRecoilState(recordState);

  useEffect(() => {
    const data = readAllRecord();

    setRecordData(data.map(record => ({...record})));
  }, [setRecordData]);

  return (
    <Style.container>
      <ScrollView>
        {/* 연간 영업 금액 */}
        <YearBusinessAmount />

        {/* 월별 운행 기록 */}
        <Style.bottomContainer>
          {/* 월별 달력 */}
          <MonthCalendar
            currentMonth={currentMonth}
            selectMonth={selectMonth}
            setSelectMonth={setSelectMonth}
          />

          {/* 이번달 영업 금액 */}
          <MonthBusinessAmount monthTotalData={monthTotalData} />

          {/* 이번달 운행 정보 */}
          <MonthRecordInfo monthTotalData={monthTotalData} />
        </Style.bottomContainer>
      </ScrollView>

      {/* create 버튼 */}
      <CreateButton />
    </Style.container>
  );
};

export default Home;

 

현재 달이면 다음달 이동 못하게 하기


이 기능은 월별 달력인 MonthCalendar에서 구현하겠습니다.

{/* 월별 달력 */}
  <MonthCalendar
    currentMonth={currentMonth}
    selectMonth={selectMonth}
    setSelectMonth={setSelectMonth}
  />

 

 

구현해야 될 기능

  1. 현재 달이면 다음 달 이동 못하게 하기
  2. 현재 달이면 다음 달로 넘기기 svg색상을 회색으로 변경하기

 

1번은 TouchableOpacity에 disabled 속성이 있어 현재달일 때

2번은 기존에 SvgXml에 설정해 주었습니다.

 

구현 코드

components/home/monthCalendar.tsx

// react, react-native
import React, {Dispatch, SetStateAction} from 'react';
import {View} from 'react-native';
import {SvgXml} from 'react-native-svg';

// library
import dayjs from 'dayjs';

// assets
import {svg} from '../../assets/svg';

// style
import {MonthCalendar as Style} from '../../styles/home.styles';
import Theme from '../../styles/Theme';

interface PropsType {
  currentMonth: string; // 현재 달
  selectMonth: string;
  setSelectMonth: Dispatch<SetStateAction<string>>;
}

// 다음달 확인 못하게 막기
const MonthCalendar = ({
  currentMonth,
  selectMonth,
  setSelectMonth,
}: PropsType) => {
  // 이전 달력으로 이동
  const onPrevPress = () => {
    const nextMonth = dayjs(selectMonth).subtract(1, 'month').format('YYYY-MM');
    setSelectMonth(nextMonth);
  };
  // 다음 달력으로 이동
  const onNextPress = () => {
    const nextMonth = dayjs(selectMonth).add(1, 'month').format('YYYY-MM');
    setSelectMonth(nextMonth);
  };
  // 현재 달로 이동
  const onCurrentPress = () => {
    setSelectMonth(currentMonth);
  };

  return (
    <Style.container>
      <Style.centerWrap>
        {/* 이전달 이동 */}
        <Style.iconButton onPress={onPrevPress}>
          <View>
            <SvgXml xml={svg.prev} />
          </View>
        </Style.iconButton>

        <Style.text>{dayjs(selectMonth).format('YYYY년 MM월')}</Style.text>

        {/* 다음달 이동 */}
        <Style.iconButton
          onPress={onNextPress}
          disabled={currentMonth === selectMonth}>
          <View>
            <SvgXml
              xml={svg.next}
              fill={
                currentMonth === selectMonth
                  ? Theme.colors.grey
                  : Theme.colors.black
              }
            />
          </View>
        </Style.iconButton>
      </Style.centerWrap>

      {/* 현재달 이동 */}
      <Style.iconButton position={true} onPress={onCurrentPress}>
        <View>
          <SvgXml xml={svg.turn} />
        </View>
      </Style.iconButton>
    </Style.container>
  );
};

export default MonthCalendar;

 

주요 코드

 <Style.iconButton
  onPress={onNextPress}
  disabled={currentMonth === selectMonth}>
  <View>
    <SvgXml
      xml={svg.next}
      fill={
        currentMonth === selectMonth
          ? Theme.colors.grey
          : Theme.colors.black
      }
    />
  </View>
</Style.iconButton>

현재달과 선택한 달이 같으면 현재달임으로 다음 달로 이동 못하게 해 주었습니다.

 

 

월간 운행 정보 데이터 구하기


 

월간 운행 정보 데이터는 MonthBusinessAmount, MonthRecordInfo에서 사용하기 때문에 Home.tsx에서 구현했습니다.

 

<<달마다 합산>>
operatingAmount : 총합 // 영업 금액
card : 총합 // 카드현금 : 총합 // 현금
lpgInjectionVolume : 총합 // LPG 주입량
lpgUnitPrice : 단가 총합 / 해당달의 개수 -> 반올림 // LPG 평균 단가
mileage : 총합 // 주행거리
businessDistance : 총합 // 영업거리 toll : 더하기 // 통행료
lpgChargeAmount : 주입량 총합 * LPG 평균 단가 // LPG 충전 금액
fuelEfficiency : 주행거리 총합 / LPG 주입량 총합 -> 반올림 // 연비
pgUsage : 주행거리 총합 / (주행거리 총합 / LPG 주입량 총합) -> 반올림 // LPG 사용량

이정보를 토대로 월간 운행 정보 데이터를 구해주었습니다.

 

구현 순서

  1. 선택한 달 데이터 필터(2024년 3월이면 2024년 3월 데이터만 뽑기)
  2. 1차로 총합만 구해도 되는 데이터 구하기
  3. 2차로 1차에서 구한 총합으로 나머지 운행 정보 구하기

 

  const currentMonth = dayjs().format('YYYY-MM'); // 현재 달: 년-월
  const [selectMonth, setSelectMonth] = useState(currentMonth);
  const [recordData, setRecordData] = useRecoilState(recordState);

  useEffect(() => {
    const data = readAllRecord();

    setRecordData(data.map(record => ({...record})));
  }, [setRecordData]);

이전에 recoil을 이용하여 realm데이터를 recoil에 넣어주었기 때문에 recoil을 이용해서 구현하겠습니다.

 

1. 선택한 달 데이터 필터

  // 선택한 달 데이터 필터
  const selectMonthData = recordData.filter(data => {
    return dayjs(data.date).format('YYYY-MM') === selectMonth;
  });

filter을 이용하여 선택한 달과 같은 데이터를 필터 했습니다.

 

2. 1차로 총합만 구해도 되는 데이터 구하기

  const monthTotalData = selectMonthData.reduce(
    (acc, curr) => {
      acc.card += curr.card; // 카드
      acc.cash += curr.cash; // 현금
      acc.lpgInjectionVolume += curr.lpgInjectionVolume; // LPG 주입량
      acc.lpgUnitPrice += curr.lpgUnitPrice; // LPG 단가
      acc.mileage += curr.mileage; // 주행거리
      acc.businessDistance += curr.businessDistance; // 영업거리
      acc.toll += curr.toll; // 통행료
      acc.operatingAmount += curr.operatingAmount; // 영업 금액
      return acc;
    },
    {
      date: selectMonth, // 날짜
      card: 0, // 카드
      cash: 0, // 현금
      lpgInjectionVolume: 0, // LPG 주입량
      lpgUnitPrice: 0, // LPG 단가
      mileage: 0, // 주행거리
      businessDistance: 0, // 영업거리
      toll: 0, // 통행료
      operatingAmount: 0, // 영업금액
      lpgChargeAmount: 0, // LPG 충전 금액
      fuelEfficiency: 0, // 연비
      lpgUsage: 0, // LPG 사용량
    },
  );

reducer를 이용해서 각 데이터의 총합을 구해주었습니다.

 

3. 2차로 1차에서 구한 총합으로 나머지 운행 정보 구하기

  // LPG 평균 단가
  monthTotalData.lpgUnitPrice = selectMonthData.length
    ? Math.round(monthTotalData.lpgUnitPrice / selectMonthData.length)
    : 0;
  // LPG 충전 금액
  monthTotalData.lpgChargeAmount =
    monthTotalData.lpgInjectionVolume * monthTotalData.lpgUnitPrice;
  // 연비
  monthTotalData.fuelEfficiency = monthTotalData.lpgInjectionVolume
    ? Math.round(monthTotalData.mileage / monthTotalData.lpgInjectionVolume)
    : 0;
  // LPG 사용량
  monthTotalData.lpgUsage =
    monthTotalData.mileage / monthTotalData.lpgInjectionVolume
      ? Math.round(
          monthTotalData.mileage /
            (monthTotalData.mileage / monthTotalData.lpgInjectionVolume),
        )
      : 0;

나머지 구해야 되는 값들은 집접대입해서 구해주었습니다.

나누기 한 값이 소수이면 안되기 때문에 반올림을 해주었고, 나눈 값이 0이면 인피니트가 나오기 때문에 나눈 값이 0이면 0으로 반환해 주었습니다.

 

전체 코드

screens/Home.tsx

// react, react-native
import React, {useEffect, useState} from 'react';
import {ScrollView} from 'react-native';

// library
import dayjs from 'dayjs';

// assets, utils, realm
import {useRecoilState} from 'recoil';
import {recordState} from '../recoil/atoms.ts';
import {readAllRecord} from '../realm/recordRealmFunctions.ts';

// component
import YearBusinessAmount from '../components/home/YearBusinessAmount.tsx';
import MonthCalendar from '../components/home/MonthCalendar.tsx';
import MonthBusinessAmount from '../components/home/MonthBusinessAmount.tsx';
import MonthRecordInfo from '../components/home/MonthRecordInfo.tsx';
import CreateButton from '../components/home/CreateButton.tsx';

// style
import {Home as Style} from '../styles/home.styles.ts';

const Home = () => {
  const currentMonth = dayjs().format('YYYY-MM'); // 현재 달: 년-월
  const [selectMonth, setSelectMonth] = useState(currentMonth);
  const [recordData, setRecordData] = useRecoilState(recordState);

  useEffect(() => {
    const data = readAllRecord();

    setRecordData(data.map(record => ({...record})));
  }, [setRecordData]);

  // 선택한 달 데이터 필터
  const selectMonthData = recordData.filter(data => {
    return dayjs(data.date).format('YYYY-MM') === selectMonth;
  });

  const monthTotalData = selectMonthData.reduce(
    (acc, curr) => {
      acc.card += curr.card; // 카드
      acc.cash += curr.cash; // 현금
      acc.lpgInjectionVolume += curr.lpgInjectionVolume; // LPG 주입량
      acc.lpgUnitPrice += curr.lpgUnitPrice; // LPG 단가
      acc.mileage += curr.mileage; // 주행거리
      acc.businessDistance += curr.businessDistance; // 영업거리
      acc.toll += curr.toll; // 통행료
      acc.operatingAmount += curr.operatingAmount; // 영업 금액
      return acc;
    },
    {
      date: selectMonth, // 날짜
      card: 0, // 카드
      cash: 0, // 현금
      lpgInjectionVolume: 0, // LPG 주입량
      lpgUnitPrice: 0, // LPG 단가
      mileage: 0, // 주행거리
      businessDistance: 0, // 영업거리
      toll: 0, // 통행료
      operatingAmount: 0, // 영업금액
      lpgChargeAmount: 0, // LPG 충전 금액
      fuelEfficiency: 0, // 연비
      lpgUsage: 0, // LPG 사용량
    },
  );

  // LPG 평균 단가
  monthTotalData.lpgUnitPrice = selectMonthData.length
    ? Math.round(monthTotalData.lpgUnitPrice / selectMonthData.length)
    : 0;
  // LPG 충전 금액
  monthTotalData.lpgChargeAmount =
    monthTotalData.lpgInjectionVolume * monthTotalData.lpgUnitPrice;
  // 연비
  monthTotalData.fuelEfficiency = monthTotalData.lpgInjectionVolume
    ? Math.round(monthTotalData.mileage / monthTotalData.lpgInjectionVolume)
    : 0;
  // LPG 사용량
  monthTotalData.lpgUsage =
    monthTotalData.mileage / monthTotalData.lpgInjectionVolume
      ? Math.round(
          monthTotalData.mileage /
            (monthTotalData.mileage / monthTotalData.lpgInjectionVolume),
        )
      : 0;

  return (
    <Style.container>
      <ScrollView>
        {/* 연간 영업 금액 */}
        <YearBusinessAmount />

        {/* 월별 운행 기록 */}
        <Style.bottomContainer>
          {/* 월별 달력 */}
          <MonthCalendar
            currentMonth={currentMonth}
            selectMonth={selectMonth}
            setSelectMonth={setSelectMonth}
          />

          {/* 이번달 영업 금액 */}
          <MonthBusinessAmount monthTotalData={monthTotalData} />

          {/* 이번달 운행 정보 */}
          <MonthRecordInfo monthTotalData={monthTotalData} />
        </Style.bottomContainer>
      </ScrollView>

      {/* create 버튼 */}
      <CreateButton />
    </Style.container>
  );
};

export default Home;

 

 

연간 영업 금액 구하기


월간 운행정보와 구현하는 방식이 비슷합니다.

 

YearBusinessAmount.tsx에 구현해 주었습니다.

 

구현 순서

  1. 현재 년도의 데이터 가져오기
  2. 현재 년도의 연간 영업 금액 구하기

 

1. 현재 년도의 데이터 가져오기

  // 현재 년도의 데이터 가져오기
  const selectYearData = recordData.filter(data => {
    return dayjs(data.date).format('YYYY') === currentYear;
  });

 

2. 현재 년도의 연간 영업 금액 구하기

  // 현재 년도의 연간 영업 금액 구하기
  const yearBusinessAmount = selectYearData.reduce((acc, curr) => {
    return acc + curr.operatingAmount;
  }, 0);

 

전체 코드

components/home/YearBusinessAmount.tsx

// react, react-native
import React from 'react';

// library
import {useRecoilValue} from 'recoil';
import dayjs from 'dayjs';

// recoil, utils
import {recordState} from '../../recoil/atoms';
import {numberCommas} from '../../utils/calculate';

// style
import {YearBusinessAmount as Style} from '../../styles/home.styles';

const MonthBusinessAmount = () => {
  const currentYear = dayjs().format('YYYY');
  const recordData = useRecoilValue(recordState);

  // 현재 년도의 데이터 가져오기
  const selectYearData = recordData.filter(data => {
    return dayjs(data.date).format('YYYY') === currentYear;
  });

  // 현재 년도의 연간 영업 금액 구하기
  const yearBusinessAmount = selectYearData.reduce((acc, curr) => {
    return acc + curr.operatingAmount;
  }, 0);

  return (
    <Style.container>
      <Style.title>연간 영업 금액</Style.title>
      <Style.valueText>{numberCommas(yearBusinessAmount)}원</Style.valueText>
    </Style.container>
  );
};

export default MonthBusinessAmount;
728x90
반응형