[#1] Dashboard 느낌으로 Task Manager 만들기

오닐·2022년 4월 7일
0

Dashboard : Task Manager

목록 보기
1/5
post-thumbnail

🌿0. 들어가기 전에

이 글에 간단하게 정리했지만 조금 더 보태 보자면, 지난번 모멘텀 클론 코딩 때 반응형으로 구현하지 못한 게 내심 걸렸다. PC에서는 잘 보여도 모바일로는 아주 난리도 아니었기 때문에 이 다음에 무언가를 만들면 꼭 반응형으로 만들겠다고 다짐했었다.
그래서 처음에는 반응형 랜딩 페이지를 만들까 했었는데, 여러 강의에서 예제로 실습해 본 적이 있어서 이번에 코딩애플 HTML 및 CSS 강의에서 처음 접한 대시보드에 손이 갔다. HTML, CSS, JavaScript를 잘 조합하면 One 페이지에서 끝나지 않고 여러 페이지를 구현할 수 있다는 점이 가장 큰 이유였다.


🌿1. 목표

우선 내가 직접 사용할 것이라고 가정하고, 지금 내게 필요한 요소가 무엇일지를 생각해 보았다. 공부하는 취준생인 만큼 시간 관리 컨셉으로 태스크 매니저를 만들면 좋을 듯 싶다.

  • 드라이브 사용 현황
    • 외장 하드 대신 구글 드라이브를 노트북과 연동하여 사용하고 있는 데에서 착안
  • 할일 목록(연, 월, 주 단위)
    • 요즘 애써 외면하고 있지만 나는 태생적으로 J이기 때문에 대충이라도 할일 목록을 만들어 두어야 한다. 올해의 목표, 월별 목표, 주 단위 스케줄 이렇게 쪼개 놓으면 시간 관리를 효율적으로 할 수 있을 것.
  • 타임 트래커
    • 하루 24시간을 어디에 어떻게 쓰고 있는지 한 눈에 볼 수 있게 정리하는 용도. 자기반성 및 동기부여에 좋을 듯.
  • 해빗 트래커, 무드 트래커, 위시 리스트
    • Task 관리와는 무관해 보이지만 그렇지 않다. 공부와 업무도 중요하지만 시간 관리도 결국에는 자기계발을 위한 것이니 나 자신에 대해 아는 것도 중요하다고 생각한다. 그리고 중간중간 쉬면서 생각을 환기시키면 능률도 올라갈 테니 태스크 매니저에 들어가기 적합하지 아니한가.
  • 다이어리
    • 포스트를 한 눈에 보는 페이지 및 포스팅하는 페이지 두 가지를 만들어 볼 생각이다. Node.js를 이용하면 페이지 내에서 실제로 포스트를 관리할 수도 있겠지만, 우선은 보여주기식 템플릿을 만드는 것이 목표. 일단 만들어 두고 나중에 백엔드 쪽 공부 좀 더 한 다음에 기능을 추가하기로 해보자.
  • 시계 또는 날씨 위젯
    • 모멘텀 때 만들어 봤으니 복습 차원에서, 그리고 완전히 내 것으로 만들기 위해서.
  • 유저 프로필, 로그인 및 회원가입, 비번 찾기 페이지, 404 페이지
    • 이쪽도 다이어리 파트랑 마찬가지로 일단은 페이지만 구성해 보기로. HTML이랑 CSS 연습 목적.

다 할 수 있을지는 모르겠지만 일단은 목표로 설정해 두고 해보기로!
더불어서 Bootstrap과 jQuery도 사용하면서 연습해 볼 예정이다. 저번에는 바닐라 JS와 퓨어 CSS였으니 이번에는 툴을 한 번 사용해 보기로.


🌿2. 폴더 구조

Home과 User account 페이지까지 구현한 현재의 폴더 구조이다.
index.html을 비롯한 html 파일은 최상위 폴더에 그대로 두고, CSS와 JS 파일은 따로 폴더를 만들어서 분리했다.
CSS와 JS는 각각 모든 페이지에서 다 사용되는 코드는 main 파일에, 특정 페이지에만 쓰이는 코드는 해당 페이지에 맞게 따로 파일을 생성했고 생성할 예정이다.

깃허브에도 추가 완료! 슬슬 Git 다루는 방법도 제대로 공부해야 하는데, 도서관에 가서 책을 한 권 빌려와야겠다...


🌿3. index.html

3-1. head, script

<!Doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Oneill's Task Manager</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <link href="https://fonts.googleapis.com/css2?family=Ubuntu&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="css/main.css">
    <link rel="stylesheet" href="css/home.css">
</head>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"
        integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="js/main.js"></script>

우선 head와 script 부분만 떼어서 보면 이렇게 생겼다.
부트스트랩을 이용할 것이기 때문에 스타터 템플릿을 베이스로 필요한 부분만 따로 추가해 주었다.

Ubuntu 폰트를 쓰기 위해 Google Font 링크를 삽입했고, 그 아래로는 CSS 파일을 추가했다. main.css에는 모든 페이지에 동일하게 사용될 스타일 코드를, home.css에는 Home 페이지에만 사용될 코드를 저장했다.

Script에는 jQuery cdn과 차트를 쉽게 넣을 수 있게 해주는 라이브러리인 chart.js, 그리고 마찬가지로 모든 페이지에서 작동할 JS 파일을 연결해주었다.

3-2. side-nav

body는 사이드 바와 메인 컨테이너 두 부분으로 나누었다.

<div class="side-nav shadow navy">
	<button aria-label="Close" class="btn-close" onclick="closeNav()">
	</button>
	</a>
    <h4 class="py-1 fw-bold blue">Task Manager</h4>
    <a href="index.html">...
    </a>
    <li class="nav-item dropdown">...
    </li>
    <a href="#">... 
    </a>
    <a href="#">...
    </a>
    <li class="nav-item dropdown">...
    </li>
    <a href="user.html">...
    </a>
</div>

사이드 바는 ①사이드 바를 닫는 버튼과 ②다른 페이지로 이동할 수 있는, 리스트 형태의 a 링크가 전부이다.

최상단의 버튼은 btn-close라는 부트스트랩 클래스를 써서 만든 사이드 바 닫기 버튼이다. 직접 X를 삽입해서 버튼을 만들 수도 있지만, border 등의 버튼 기본 스타일을 조정하지 않고 한 번에 만들 수 있어서 사용했다. onclick을 이용해서 미리 만들어 둔 JS와 바인딩하였다.

<a href="javascript:void(0)" class="btn-close" onclick="closeNav()">
</a>

구글링을 하다 보니 닫기 버튼을 만드는 방법이 몇 개 있었는데, 그 중에 하이퍼링크를 사용하는 방법도 있었다. href에 javascript:void(0)를 넣어서 이동 기능을 무효화시켜서 버튼을 만든 것으로 적용하면 <button>과 똑같이 작용하지만, 검색 엔진 최적화에 도움이 되지 않을 듯하여 button을 이용했다. 더불어 버튼 자체에는 스크린 리더가 읽을 만한 정보가 없기 때문에 aria-label을 추가했다.

<a href="index.html">
	<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"
                class="bi bi-house-heart-fill" viewBox="0 0 16 16">
		<path fill-rule="evenodd"
                    d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.707L8 2.207l6.646 6.646a.5.5 0 0 0 .708-.707L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.707 1.5Z" />
		<path fill-rule="evenodd"
                    d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293l6-6Zm0 5.189c1.664-1.673 5.825 1.254 0 5.018-5.825-3.764-1.664-6.691 0-5.018Z" />
	</svg>
	Home
</a>

아래 메뉴는 이렇게 생긴 하이퍼링크가 여러 개 담겨 있다. 다 만든 Home과 My Account 페이지도 해당 메뉴에 연결시켰고, 아직 만들지 않은 페이지는 href에 #을 넣어서 다른 곳으로 이동되지 않게 했다.
링크 안에 부트스트랩 아이콘을 svg 형태로 추가해서 한 눈에 어떤 메뉴인지 알아보기 쉽게 만들었다. 웹 폰트와 함께 사용하면 코드를 줄일 수 있던데, 어떻게 쓰는 건지 공부를 해서 코드 길이를 줄여야겠다.

<li class="nav-item dropdown">
	<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
		<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-list-ul"
                    viewBox="0 0 16 16">
			<path fill-rule="evenodd"
				d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
		</svg>
        Trackers
	</a>
    <ul class="dropdown-menu">
		<li>
			<a href="#">...
                Time Tracker</a>
		</li>
        <li>...
        </li>
        <li>...
        </li>
	</ul>
</li>

아래 리스트 형식의 메뉴 중 같은 범주로 묶을 수 있는 것들은 dropdown 처리했다. 타임 트래커, 해빗 트래커, 무드 트래커는 Trackers로 묶었고, 다이어리 한 눈에 보기와 쓰기 페이지는 Diary로 묶었다. 이것 역시 부트스트랩을 사용하면 쉽게 구현 가능하다.
흠, 근데 생각해 보니 dropdown은 CSS만으로는 만들어 본 적이 없구나. 다른 페이지에서 한 번 만들어 봐야겠다.

3-3. main-container / header

<div class="main-container">
	<div class="header d-flex shadow p-2 mb-4 bg-body rounded">...
	</div>
	<div class="welcome-box shadow p-4 mb-4 bg-body row rounded gx-0">...
    </div>
	<div class="plan-box row">...
	</div>
	<footer class="footer">
	</footer>

메인 컨테이너는 header, welcome-box, plan-box, footer로 나누었다.
header에는 사이드 바를 열 수 있는 토글 버튼과 검색창, 프로필 바가 있다.
welcome-box는 말 그대로 나를 환영해 주는 멘트가 담긴 박스다.
plan-box는 연, 월, 주 단위의 계획을 한 눈에 보고 해당 페이지로 이동할 수 있게 구성되어 있다.
footer는 그냥 내가 만들었다는 표시만 해두었다.

<button class="navbar-toggler navy" onclick="openNav()">
	<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor" class="bi bi-list"
                    viewBox="0 0 16 16">
		<path fill-rule="evenodd"
			d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z" />
	</svg>
</button>

우선 header의 토글 버튼은 사이드 바를 여는 기능만 있다. 반응형 웹으로 만드느라 1200px 이상의 PC 전체 화면에서는 사이드 바가 좌측에 고정되어 있다가 모바일 화면 정도로 작아지면 자연스럽게 사라지고 토글 버튼이 생기게 만들다 보니 그렇게 됐다. 그에 따라 사이드 바도 좌측에 고정됐을 때는 닫기 버튼이 없다가 토글로 열 수 있게 됐을 때는 닫기 버튼이 생기도록 만들었다. 그래서 닫기 버튼과 토글 버튼에는 각각 사이드 바를 닫는 기능과 여는 기능밖에 없다! 열고 닫기를 한꺼번에 하려면 classList와 toggle을 같이 쓰면 되겠지?

<div class="head-search">
	<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-search"
                    viewBox="0 0 16 16">
		<path
			d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" />
	</svg>
	<form>
		<input class="form-control" type="search" placeholder="Search" aria-label="Search">
	</form>
</div>

다음은 검색 창...인데 바로 검색 엔진으로 연결하는 것은 할 줄 알지만 만든 페이지 내부를 탐색하는 것은 아직 할 수 없어서 껍데기만 만들어 두었다.

<div class="head-profile ms-auto">
	<div class="dropdown">
		<button class="btn dropdown-toggle navy p-0 border-0" type="button" id="dropdownMenuButton1"
			data-bs-toggle="dropdown" aria-expanded="false">
            <img src="images/1.png" alt="profile-pic">
            Oneill
        </button>
        <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
        	<li>
            	<a class="dropdown-item" href="user.html">...
              	</a>
          	</li>
          	<li>
            	<a class="dropdown-item" href="#">...
              	</a>
          	</li>
          	<li>
            	<a class="dropdown-item" href="#">...
              	</a>
          	</li>
          	<li>
            	<a class="dropdown-item" href="#">...
              	</a>
          	</li>
          	<li>
            	<a class="dropdown-item" href="#">...
              	</a>
          	</li>
		</ul>
	</div>
</div>

다음은 header 우측의 프로필 바다.
dropdown 버튼 부분에 이름과 프로필 사진을 넣은 후, 사이드 바와 마찬가지로 dropdown 리스트를 구성했다. 여기에도 하이퍼링크 안에 부트스트랩 아이콘을 넣었다.


🌿4. main.js

function openNav() {
    $(".side-nav").css("width", "240px");
}

function closeNav() {
    $(".side-nav").css("width", "0px");
}

사이드 바를 열고 닫는 기능이다.
사이드 바 역할을 하는 div를 안 보이는 위치에 숨겨 두었다가 transform: translateX()로 위치를 조정할까 하다가 width를 조절하는 방법도 괜찮겠다 싶어서 적용해 보았다.
jQuery를 사용해서 코드가 짧아졌다!


🌿5. main.css

div {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: 'Ubuntu', sans-serif;
    background-color: #E7ECEF;
    color: #274C77;
}

html {
    line-height: 1.15;
}

li {
    list-style-type: none;
}

hr {
    margin: 10px 0;
}

모든 페이지에 적용될 기본 사항을 미리 설정해 두었다.
div에 이런저런 스타일을 설정해도 div 자체의 크기를 넘지 않도록 border-box를 설정하고, body에 color 속성을 부여 해서 body 내부의 텍스트 색상을 통일했다.
텍스트 줄 간격도 하나로 통일하고, list 앞에 붙는 마커를 없애기 위해 none을 적용했다. 구분선 역할을 하는 hr도 위아래로 margin 값을 주어 다른 요소들과 너무 붙지 않게끔 설정했다.

.lightgray {
    color: #E7ECEF;
}

.navy {
    color: #274C77;
}

.blue {
    color: #6096BA;
}

.skyblue {
    color: #A3CEF1;
}

.darkgray {
    color: #8B8C89;
}

색상도 미리 봐둔 팔레트의 색상을 class로 지정해서 필요한 곳에 적용할 수 있게 만들었다.

.side-nav {
    width: 240px;
    height: 100%;
    position: fixed;
    top: 0;
    z-index: 1;
    background-color: white;
    padding-top: 20px;
    transition: 0.5s;
    overflow-x: hidden;
}

.side-nav a {
    display: flex;
    align-items: center;
    text-decoration: none;
    font-size: 18px;
    padding: 10px 30px 10px 32px;
    color: #274C77;
    transition: 0.3s;
}

.side-nav a:hover,
.side-nav a:focus {
    color: #6096BA;
}

.side-nav svg {
    margin-right: 15px;
}

.side-nav .btn-close {
    position: absolute;
    top: 30px;
    right: 20px;
    padding: 0;
    display: none;
}

.side-nav .dropdown-menu {
    border: 0;
    transition: 0.4s;
}

사이드 바와 header는 모든 페이지에 고정으로 들어가는 부분이라 main.css에 스타일 코드를 짰다. div 크기에 상관없이 body 전체 높이를 차지할 수 있도록 height: 100%를 적용했고, 열고 닫을 때 다른 요소들보다 앞쪽에 있도록 z-index도 적용했다.
스크롤을 움직여도 원래 위치에 고정되도록 position: fixed를 주었고, 열고 닫힐 때 부드럽게 움직이도록 transition을 적용했다.
열고 닫는 JS 코드를 div 이동이 아니라 width 조정으로 만들었기 때문에 width가 0이 될 때는 없어진 영역에 포함된 텍스트를 감추기 위해서 overflow-x: hidden을 적용했다. 근데 어디서 듣기로는 overflow를 남용하지 말라던데, 다음에는 translate를 이용해서 위치 조절을 해봐야겠다.

사이드 바의 메뉴 역할을 하는 링크에도 스타일을 적용했다.
각 링크에 아이콘을 삽입했기 때문에 둘을 같은 줄에 정렬하기 위해 flex와 align-items를 적용했고, 밑줄이나 클릭 시 색이 바뀌는 등의 효과를 없애기 위해 text-decoration: none을 적용했다.

메뉴에 마우스를 올리거나 focus를 주었을 때를 구분하기 위해 색상을 바꿔주었고, 사이드 바의 닫기 버튼의 위치를 부모 요소인 side-bar와 무관하게 조절하기 위해 position: absolute를 주었다. 그리고 PC 화면일 때는 사이드 바가 열린 채로 고정되어 있기 때문에 닫기 버튼이 보이지 않도록 display: none을 적용했다.

@media screen and (max-width: 768px) {
    .main-container {
        margin-left: 0;
    }

    .side-nav {
        width: 0;
    }

    .side-nav .btn-close {
        display: block;
    }

    .navbar-toggler {
        display: block;
    }
}

그리고 미디어 쿼리를 이용해서 화면의 너비가 768px보다 작을 때는 사이드 바가 닫히고 사이드 바를 열 수 있는 토글 버튼이 드러나도록 만들었다. 동시에 사이드 바에 닫기 버튼도 보이도록 만들었다.

.main-container {
    margin-left: 240px;
    padding: 20px;
}

.header input:focus {
    outline: none;
}

.navbar-toggler {
    display: none;
    padding: 0;
    margin-right: 5px;
}

.head-search {
    display: flex;
    width: 300px;
    align-items: center;
}

.head-search svg {
    margin-left: 10px;
    margin-right: 5px;
    color: #274C77;
}

.head-search input {
    border: 0;
    color: #274C77;
}

.head-search input:focus,
.dropdown button:focus {
    box-shadow: none;
}

.head-profile {
    display: flex;
    align-items: center;
}

.head-profile .dropdown-menu a {
    display: flex;
    align-items: center;
    font-size: 15px;
    padding: 10px 0 5px 15px;
}

.dropdown {
    margin-right: 10px;
}

.dropdown-item {
    color: #274C77;
}

.dropdown-item span {
    margin-left: 10px;
    margin-right: 15px;
}

.dropdown img {
    width: 30px;
    margin-right: 5px;
}

.dropdown-menu svg {
    margin-right: 10px;
}

사이드 바가 열린 상태로 고정되어 있으니, 거기에 가려지지 않도록 메인 컨테이너의 위치를 조절해주었다. 여기서 조정한 margin값은 미디어 쿼리 적용 시 0으로 바뀌어서 해당 크기가 되면 메인 컨테이너는 감춰진 사이드 바의 자리만큼 이동하게 된다.

대부분은 부트스트랩 스타일을 바꾸거나 위치를 조절하는 가운데 조금 특이한 부분은 검색창과 dropdown 버튼에 focus가 갔을 때 파란 그림자가 생기는 것을 제거하기 위해 box-shadow 속성을 none으로 바꾸었다. 처음에는 border나 outline인 줄 알고 두 속성을 바꾸었지만 변화가 없어서 개발자 도구를 열어 보니 그림자였다!


🌿5. 가벼운 회고

Home 전체를 하나의 포스팅에 다 담고 싶었는데 생각보다 코드 양이 방대해서 우선은 모든 페이지에 고정적으로 들어갈 사이드 바와 헤더만 포스팅했다.
만들기 전에 예상한 것보다 시간이 오래 걸리기는 했지만, Home을 잘 만들어 두니 그 다음으로 만든 My Account 페이지는 시간이 얼마 걸리지 않았다. 이대로 지치지 않고 할 수 있는 데까지는 다 해서 예쁜 포트폴리오가 나왔으면 좋겠다.
그리고 결과물뿐만 아니라 나 자신도 포트폴리오를 만들기 전보다 훨씬 성장해 있기를!

0개의 댓글