만드는 사람이 수고로운 UX 개발기

Dec.26.2022 이찬호

Web Frontend

[그림 1] 우아한형제들 사무실 어디에서든 만날 수 있는 문구. 가장 좋아하는 문구입니다.

들어가며

만드는 사람이 수고로우면 쓰는 사람이 편하고, 만드는 사람이 편하면 쓰는 사람이 수고롭다.

우아한형제들 사무실에서 쉽게 발견할 수 있는 문구입니다. 이 문구를 볼 때마다 나의 게으름이 서비스 사용자를 수고롭게 하는 건 아닌지 생각해 보게 되는데요.

제가 개발하고 있는 서비스 SCM(Supply Chain Management, 공급망관리)은 발주를 넣는 내부 직원과 공급사 분들이 사용하는 백오피스 서비스입니다. 백오피스 서비스 특성상 디자이너가 없이 개발되는 경우가 대부분이고, 서버 개발자가 기능만 동작하게 개발해서 사용하는 경우도 매우 많습니다. 사용성은 우선순위에 밀려 기능만 돌아가게 만들곤 하죠.

사용자용 서비스와 다르게, 백오피스 서비스는 사용자들이 이 서비스로 하루 종일 업무를 봅니다. 사용성이 고려되지 않은 서비스로 불편함을 감수하면서 하루 종일 고통받으며 업무를 하게 되지요. 심지어는 불편한 기능을 잘 활용하는 방법이 사용자들 사이에서 노하우로 공유되기도 합니다.

사용자 경험(UX), 개발 경험(DX)이 중요한 만큼 저는 백오피스 서비스에 더 나은 UX를 제공해 업무를 보는 분들의 업무 경험이 좋아지길 바랍니다. UX를 개선해 사용하기가 편하고, 휴면 에러도 방지한다면 더 나아진 업무 환경이 업무 생산성을 높일 수 있습니다.

이 글에서는 백오피스 서비스 사용자가 수고롭지 않게 하기 위해 기획서 이상으로 추가로 고안해서 반영한 4가지 UI/UX를 공유합니다. 기능 요구사항이 정리되고, 기획서가 나와서 기본 기능을 다 개발한 후에 프론트엔드 개발자 입장에서 신경 쓸 수 있는 부분을 추가로 개발해서 반영한 부분입니다.

1. 입력 중인 상태 보호, 편집됨

첫 번째로 소개해드릴 UX는 편집됨입니다. 우리가 자주 사용하는 프로그램에서는 기본적으로 편집 중인지를 체크하고 사용자가 창을 닫으려 할 때 수정 중인 내용에 대한 처리를 묻습니다.

2-1.keynote 편집됨
[그림 2-1] keynote 사례. 편집됨 표기 [그림 2-2] SCM에 반영한 편집됨 표기

SCM은 정보가 많은 데이터를 다루기 때문에 데이터를 입력하는 도중에 페이지를 새로고침 하거나, 모달에서 작업 중일 때 모달을 닫으면 데이터가 날아갈 수 있습니다. 이를 방지하기 위해 사용자 입력이 있는 곳마다 편집됨 상태를 넣었습니다.

3-1.vscode 편집중 닫기 3-2.scm 편집중 닫기
[그림 3-1] vscode 사례. 닫기 방지 컨펌 [그림 3-2] SCM에 반영한 닫기 방지 컨펌

편집 중인 탭 혹은 모달을 닫으려고 하면 변경 중인 내용이 있다는 것을 사용자에게 알리고, 정말로 닫겠냐고 묻습니다. 취소를 누르면 작업 중이던 화면을 그대로 보여주고, 확인을 누르면 작업 중인 탭 혹은 모달을 닫습니다.

const isEditing = () => {
   return !equal(
     JSON.stringify(data),
     JSON.stringify(checkData)
   )
 }

편집됨 상태를 보여주기 위해서는, 이를 체크 할 수 있는 checkData가 필요하고 입력 중인 데이터와 같은 값인지 비교해주어야 합니다.

const onCloseModal = async () => {
   if (isEditing()) {
     const isClose = await new Dialog().confirm(
       `[업체 등록]탭을 닫으시겠습니까?`,
       '변경사항이 저장되지 않습니다.'
     )
     if (isClose) {
       onCancel()
     return
     }
   }
   onCancel()
 }

isEditing 함수를 만들어서 모달 혹은 페이지를 닫는 함수의 맨 앞에 넣고 편집 중인지를 확인합니다. 편집 중이라면, 알림 창이 나타나 사용자에게 정말 닫을 것인지 확인합니다. 이 기능을 통해 실수로 화면을 새로고침 하거나, 작업 중인 모달을 닫으려고 하는 경우에도 안전하게 입력 중이던 데이터를 보호할 수 있습니다.

2. 유효성 검증 시각화, ValidationItem

[그림 4] 페이지에서 사용 예시. 필수 값을 입력했을 때 실시간으로 입력 여부를 보여준다.

SCM은 서비스 특성상 많은 데이터를 다루다 보니 필수로 입력해야 하는 값에 대한 유효성 검사를 잘 보여줘야 했습니다. 그래서 고안해 낸 것이 ValidationItem입니다. ValidationItem은 필수로 입력해야 하는 값에 유효성 검사를 걸어두고, 사용자가 내용을 올바르게 채웠는지 피드백해 주는 컴포넌트입니다. ValidationItem을 고안한 이유는, 최대한 사용자에게 빠른 피드백을 주고 싶었습니다. 또 어떤 입력을 더 해야 프로세스가 진행되는지를 직관적으로 한눈에 보여주고 싶었습니다. 이를 통해 SCM을 사용하는 사용자의 작업 시간을 줄이고 싶었습니다.

[그림 5] 모달에서 사용 예시.

등록하는 페이지 혹은 등록하는 모달에서도 사용하고, 상태 변경을 해야 하는 상황에서 안내용으로 사용합니다. ValidtaionItem은 간단하게 label과 valid 여부만 전달받습니다. [그림 5]에서 유효성을 보여주는 데 사용한 ValidationItem 코드는 다음과 같습니다.

<Horizontal gap={4}>
  <ValidationItem label="이메일" valid={isEmail(userData)} />
  <ValidationItem label="중복확인" valid={isEmailCheck()} />
  <ValidationItem label="구분" valid={isType(userData)} />
  <ValidationItem label="이름" valid={isName(userData)} />
  <ValidationItem label="휴대전화" valid={isPhone(userData)} />
  <ValidationItem label="역할 부여" valid={isRole(userData)} />
</Horizontal>

isOOO 함수는 추가 버튼을 활성화하는 유효성 코드로도 같이 사용됩니다. 기존 유효성에 시각화를 추가한 것이 ValidationItem 입니다.

[그림 4] 예시처럼 처음엔 페이지 단위의 등록 프로세스에서도 사용했으나, 등록하는 데 필요한 필수 입력 요소가 너무 많아지면 아이콘을 나열해서 다 보여줄 수 없어서 현재는 입력이 많지 않은 모달이나, 안내용 툴팁에서 사용하고 있습니다.

3. 파일 다운로드 아이콘

다음은 SCM에서 기존에 사용하고 있던 파일 다운로드 버튼들입니다.

6-1. 파일 예제1 6-2. 파일 예제2 6-3. 파일 예제3
그림 6-1. 다운로드 버튼 그림 6-2. pdf 아이콘 버튼 그림 6-3. 파일이름 링크 버튼

백오피스 서비스답게 파일을 올리고 다운받아야 할 일이 많습니다. 파일 다운로드를 [파일 다운로드] 버튼(그림 6-1), 혹은 파일 이름으로 된 링크(그림 6-3)로 사용했는데, 일관성도 부족하고 직관적이지 않았습니다. 이를 해결 하기 위해 파일 아이콘을 만들었습니다. 파일이라는 것과 파일의 확장자까지 한눈에 보여주고 싶었습니다.

[그림 7] 직접 그린 아이콘들. 어디서 본것 같다면 본게 맞습니다. 저도 그걸 보고 비슷하게 디자인 했어요.

아이콘을 만들어야겠다는 생각이 들어서 아이콘을 찾아보았으나, 마음에 드는 파일 아이콘을 찾을 수 없어서 피그마를 이용해 직접 그렸습니다. 직관적으로 파일임을 알 수 있는 디자인 위에 비슷한 유형의 확장자끼리 색을 구분하여 표시했습니다.

8-1.icon hover 8-2.icon click loading
[그림 8-1] 기본 상태 , 마우스 hover 시 [그림 8-2] 클릭 시 로딩 상태

또한, 다운로드 요청을 기다리는 동안 파일 아이콘 위로 로딩을 노출시켜 여러번 누르지 않도록 사용자 경험을 개선했습니다.

  <FileButton
    fileName={fileName}
    loading={fileLoading}
    onClick={() => handleDownloadFile({ item })}
  />

FileButton 컴포넌트에 fileName만 전달하면 파일 확장자를 분석하여 적절한 아이콘을 자동으로 표시합니다. 컴포넌트에 마우스 커서를 올리면 툴팁 UI에서 파일 이름도 확인할 수 있죠.

4. 스크롤 위치

[그림 9] 스크롤 위치가 맨 위로 올라가는 안좋은 예시

웹서핑 도중 뒤로 가기 버튼으로 돌아오면, 스크롤이 맨 위로 올라가버리는 경험해 보지 않았나요? SCM은 탭 UI를 제공하고 있어서 탭마다 페이지가 보이고, 페이지마다 스크롤이 생깁니다. 각 탭에 있는 페이지마다 스크롤이 각각 다르게 동작하죠. 탭을 이동할 때마다 모든 탭(페이지)의 스크롤이 매번 맨 위로 올라온다면 복잡한 화면을 보다가 어디를 보고 있었는지 길을 잃는 분들이 많아질 겁니다.

이 문제를 해결하기 위해 세션 스토리지에 탭(페이지)별로 스크롤 위치를 기억해두었다가 탭이 활성화될 때 스크롤을 저장된 위치로 이동시키도록 구현했습니다.

const lastScrollPositionSessionStorageKey = 'page-scroll-position'

useEffect(() => {
   // 로컬스토리지에 등록된 스크롤 위치 호출
   const lastScrollPosition = window.sessionStorage.getItem(
     lastScrollPositionSessionStorageKey
   )

   // 스크롤 위치를 로컬스토리지에 저장
   function onScroll() {
     const scrollTop = ref.current?.scrollTop ?? 0
     window.sessionStorage.setItem(
       lastScrollPositionSessionStorageKey,
       JSON.stringify({
         ...JSON.parse(lastScrollPosition),
         [tab.key]: scrollTop
       })
     )
   }
   // 탭이 활성화 되면 scroll 이벤트 등록
   if (isTabActivated(myself)) {
     ref.current?.addEventListener('scroll', onScroll)
   }

   // 탭 이동시 자기 탭 스크롤 위치로 세팅
   if (lastScrollPosition) {
     const parsedScrollPosition = JSON.parse(lastScrollPosition)
     ref.current?.scrollTo({ top: parsedScrollPosition[tab.key] })
   }
 }, [tabHistory])

scroll 이벤트에 onScroll 함수를 실행하게 한 후, 현재 탭키와 현재 스크롤 위치를 객체로 저장해둡니다. 탭을 활성화할 때 (열려있는 탭을 누르기, 뒤로가기/앞으로가기) 해당 탭 키의 스크롤 위치를 불러와 스크롤 위치를 변경하여 현재 스크롤 위치를 유지해줍니다.

마치며

이제 출발선에 섰습니다. 백오피스를 사용하는 사용자들은 업무 외 시간에는 이미 최고의 UI/UX가 반영된 서비스를 사용합니다. 이 사용자들 입장에서는 원래 되어야 하는 UX가 드디어 반영되었습니다.

‘어차피 쓰는 사람도 많지 않은 백오피스 서비스인데 이렇게까지 해야 하나요?’ 의문이 들 수 있습니다. 하지만 이 수고를 통해 업무를 보는 분들의 생산성이 늘 수 있다면 기꺼이 수고로울 가치가 있다고 생각합니다. 이런 좋은 UX를 쌓고 쌓아서 업무를 보는 분들이 업무 경험이 좋다고 느끼게 만들고 싶습니다. 그것이 ‘배민다움’이니까요.