動画はこちら↓
<template>
<div class="todoApp">
<TodoInput></TodoInput>
<TodoListView></TodoListView>
</div>
</template>
<script setup>
import TodoInput from "./TodoInput.vue";
import TodoListView from "./TodoListView.vue";
</script>
<style scoped>
.todoApp > * {
margin: 2rem 0 0 0;
}
</style><template>
<div>
<p v-if="isErrMsg">タスク・期限を両方入力してください。</p>
<form @submit="onSubmitForm">
<label>やること<input type="text" v-model="input" /></label><br />
<label>期限<input type="date" v-model="inputDate" /></label><br />
<input class="submit" type="submit" value="登録!" />
</form>
</div>
</template>
<script setup>
import { ref } from "vue";
import { statuses } from "../const/statuses";
const input = ref("");
const inputDate = ref("");
const isErrMsg = ref(false);
function onSubmitForm() {
if (input.value == "" || inputDate.value == "") {
isErrMsg.value = true;
event.preventDefault();
return;
}
const items = JSON.parse(localStorage.getItem("items")) || [];
const newItem = {
id: items.length,
content: input.value,
limit: inputDate.value,
state: statuses.NOT_START,
onEdit: false,
};
items.push(newItem);
localStorage.setItem("items", JSON.stringify(items));
}
</script>
<style scoped>
input {
width: 70%;
}
label {
display: flex;
justify-content: space-between;
}
.submit {
width: 100%;
}
</style><template>
<div>
<div v-if="isShowModal" class="modal">
<div class="modal-content">
<p>{{ deleteItemContent }}を削除してもよろしいですか?</p>
<button @click="onDeleteItem()">はい</button>
<button @click="onHideModal()">キャンセル</button>
</div>
</div>
<p v-if="isErrMsg">{{ errMsg }}</p>
<table>
<tr class="title">
<th class="th-id">ID<button @click="sortById()">↓</button></th>
<th class="th-value">やること</th>
<th class="th-limit">期限<button @click="sortByLimit()">↓</button></th>
<th class="th-state">状態</th>
<th class="th-edit">編集</th>
<th class="th-delete">削除</th>
</tr>
<!--タスクの件数分表示-->
<tr
v-for="item in items"
:key="item.id"
:class="{ red: new Date(item.limit) < today }"
>
<td>{{ item.id }}</td>
<td>
<span v-if="!item.onEdit">{{ item.content }}</span>
<input v-else v-model="inputContent" type="text" />
</td>
<td>
<span v-if="!item.onEdit">{{ item.limit }}</span>
<input v-else v-model="inputLimit" type="date" />
</td>
<td>
<span v-if="!item.onEdit">{{ item.state.value }}</span>
<select v-else v-model="inputState">
<option
v-for="state in statuses"
:key="state.id"
:value="state"
:selected="state.id == item.state.id"
>
{{ state.value }}
</option>
</select>
</td>
<td>
<button class="btn" v-if="!item.onEdit" @click="onEdit(item.id)">
編集
</button>
<button class="btn" v-else @click="onUpdate(item.id)">完了</button>
</td>
<td>
<button class="btn" @click="showDeleteModal(item.id)">削除</button>
</td>
</tr>
<!--タスクの件数分表示-->
</table>
</div>
</template>
<script setup>
import { statuses } from "../const/statuses";
import { ref } from "vue";
let items = ref(JSON.parse(localStorage.getItem("items")) || []);
let inputContent = ref(); //タスクの内容
let inputLimit = ref(); //タスクの期限
let inputState = ref(); //タスクのステータス
let isErrMsg = ref(false);
let isShowModal = ref(false);
let errMsg = ref(""); //エラーメッセージの内容
let deleteItemId = ""; //削除対象のItemのID
let deleteItemContent = ref(); //削除対象のItemの内容
const today = new Date();
function onEdit(id) {
let isOnEditOther = false;
items.value.map((item) => {
if (item.onEdit) {
isOnEditOther = true;
return;
}
});
if (isOnEditOther) {
errMsg.value = "他に編集中のタスクがあります";
isErrMsg.value = true;
return;
}
inputContent.value = items.value[id].content;
inputLimit.value = items.value[id].limit;
inputState.value = items.value[id].state;
items.value[id].onEdit = true;
}
function onUpdate(id) {
if (inputContent.value == "" || inputLimit.value == "") {
errMsg.value = "タスクの内容と期限を入力してください。";
isErrMsg.value = true;
return;
}
const newItem = {
id: id,
content: inputContent.value,
limit: inputLimit.value,
state: inputState.value,
onEdit: false,
};
items.value.splice(id, 1, newItem);
localStorage.setItem("items", JSON.stringify(items.value));
isErrMsg.value = false;
}
function showDeleteModal(id) {
isShowModal.value = true;
deleteItemId = id;
deleteItemContent = items.value[id].content;
}
function onDeleteItem() {
//タスクを削除する処理
items.value.splice(deleteItemId, 1);
//IDを振り直す
items.value = items.value.map((item, index) => ({
id: index,
content: item.content,
limit: item.limit,
state: item.state,
onEdit: item.onEdit,
}));
localStorage.setItem("items", JSON.stringify(items.value));
isShowModal.value = false;
}
function onHideModal() {
isShowModal.value = false;
}
function sortByLimit() {
//日付でソートする
items.value.sort((a, b) => new Date(a.limit) - new Date(b.limit));
localStorage.setItem("items", JSON.stringify(items.value));
}
function sortById() {
//IDでソートする
items.value.sort((a, b) => a.id - b.id);
localStorage.setItem("items", JSON.stringify(items.value));
}
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 8px;
}
.red {
color: red;
}
table > * > th {
width: 16.666%;
}
table {
width: 100%;
}
.btn {
width: 100%;
}
button {
border: none;
border-radius: 5px;
}
.title {
background-color: rgb(158, 212, 158);
}
</style>