--- /dev/null
+<template>
+ <div class="bg-gray-50">
+ <div class="max-w-xl mx-auto py-1 divide-y md:max-w-4xl">
+ <div class="py-10 px-5 rounded-md shadow-lg">
+ <div>Todo タイトル</div>
+ <input type="text"
+ list="todo_header"
+ v-model="titleName"
+ @input="onHeaderChange"
+ class="w-10/12 px-1 py-2 mb-1 border-b border-gray-500 focus:outline-none focus:border-indigo-500 transition-colors text-gray-900" />
+ <button class="bg-gray-200 w-12 h-12 px-3 py-2 mb-1 ml-0 text-sm font-bold tracking-wider text-gray hover:bg-gray-400 inline-flex items-center justify-center"
+ @click="searchHeader">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-check" 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-.5zM3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/>
+ </svg>
+ </button>
+ <button class="bg-blue-500 w-10 h-10 p-3 ml-3 text-sm font-bold tracking-wider text-white rounded-full hover:bg-red-600 inline-flex items-center justify-center"
+ v-if="selectedOption == null"
+ @click="addHeader">
+ <svg xmlns="http://www.w3.org/2000/svg" class="fill-current" viewBox="0 0 24 24">
+ <path d="M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z"/>
+ </svg>
+ </button>
+ <button class="bg-blue-500 w-10 h-10 p-3 ml-3 text-sm font-bold tracking-wider text-white rounded-full hover:bg-red-600 inline-flex items-center justify-center"
+ v-else
+ @click="deleteHeader">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
+ <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
+ <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
+ </svg>
+ </button>
+
+ </div>
+ <div class="py-10 px-5 rounded-md shadow-lg">
+ <div>Todo リスト</div>
+ <div v-if="loading()">Loading...</div>
+ <div v-else class="py-1" v-for="list in todo_list" :key="list.id">
+ <todo :data="list"
+ @onCheckChange="onCheckChange"
+ @onTextChange="onTextChange"
+ @onDelete="onDelete"/>
+ </div>
+ <div class="py-5">
+ <button
+ class="p-0 w-10 h-10 bg-blue-600 rounded-full hover:bg-blue-700 active:shadow-lg mouse shadow transition ease-in duration-200 focus:outline-none"
+ @click="onAdd"
+ v-if="selectedHeadrId != 0">
+ <svg viewBox="0 0 20 20" enable-background="new 0 0 20 20" class="w-6 h-6 inline-block">
+ <path fill="#FFFFFF" d="M16,10c0,0.553-0.048,1-0.601,1H11v4.399C11,15.951,10.553,16,10,16c-0.553,0-1-0.049-1-0.601V11H4.601
+ C4.049,11,4,10.553,4,10c0-0.553,0.049-1,0.601-1H9V4.601C9,4.048,9.447,4,10,4c0.553,0,1,0.048,1,0.601V9h4.399
+ C15.952,9,16,9.447,16,10z" />
+ </svg>
+ </button>
+ </div>
+ </div>
+
+ <select-box/>
+
+ <snack-bar :msg="snackText" ref="snackBar"/>
+ <select-panel
+ :dataList="todo_header"
+ title="Todoリスト選択"
+ ref="selectPanel"
+ @onSelected="onSelected"/>
+
+ <spinner ref="spinner"/>
+
+ </div>
+ </div>
+</template>
+
+<script>
+
+import moment from 'moment'
+import _ from 'lodash' // eslint-disable-line no-unused-vars
+//import { useQuery, useResult } from '@vue/apollo-composable' // eslint-disable-line no-unused-vars
+import gql from 'graphql-tag'
+import selectBox from '@/components/selectBox.vue'
+import todo from '@/components/todo.vue'
+import snackBar from '@/components/snackBar.vue'
+import selectPanel from '@/components/selectPanel.vue'
+import spinner from '@/components/spinner.vue'
+
+export default {
+ name: 'todoApp',
+ components: {
+ selectBox
+ , todo
+ , snackBar
+ , selectPanel
+ , spinner
+ },
+ beforeMount() {
+ this.$store.dispatch('test');
+ this.$apollo.queries.todo_list.refetch({
+ selectedHeadrId: 0
+ })
+ },
+ computed: {
+ // 選択中のオプションオブジェクト。任意入力値の場合は null を返す
+ selectedOption() {
+ if(!this.todo_header){
+ return null
+ }
+ const selected = this.todo_header.find(item => item.name === this.titleName)
+ return selected || null
+ }
+ },
+ //Vue2.6なのでsetupが…
+ // setup() {
+ // // const { result, loading, error, refetch } = useQuery(gql`
+ // // query todo_header {
+ // // todo_header {
+ // // id
+ // // name
+ // // }
+ // // }
+ // // `)
+
+ // // const users = useResult(result)
+
+ // // return {
+ // // users,
+ // // loading,
+ // // error,
+ // // refetch,
+ // // }
+ // onBeforeMount(() => {
+ // console.log('Component is onBeforeMount!')
+ // // this.$apollo.queries.todo_list.refetch({
+ // // selectedHeadrId: 0
+ // // })
+ // })
+ // },
+ methods: {
+ selectedTitle(id){
+ this.reFetch(id)
+ },
+ onSelected(head){
+ this.titleName = head.name
+ this.selectedTitle(head.id)
+ },
+ onHeaderChange() {
+ if(this.selectedOption){
+ this.selectedTitle(this.selectedOption.id)
+ } else {
+ this.$apollo.queries.todo_list.refetch({
+ selectedHeadrId: 0
+ })
+ this.selectedHeadrId = 0
+ }
+ },
+ addHeader(){
+ if(_.isEmpty(this.titleName)){
+ this.$refs.snackBar.setMessageAndshow('タイトルが入ってないよ!')
+ return
+ }
+
+ },
+ deleteHeader(){
+
+ },
+ searchHeader(){
+ this.$refs.spinner.show()
+ this.$apollo.queries.todo_header.refetch()
+ .then(() => {
+ this.$refs.selectPanel.show()
+ }).catch((error) => {
+ this.$refs.snackBar.setMessageAndshow('db error?')
+ console.error(error)
+ }).finally(() => {
+ this.$refs.spinner.close()
+ })
+
+ },
+ onCheckChange(data) {
+ this.updateMutate(data);
+ },
+ onTextChange(data) {
+ this.updateMutate(data);
+ },
+ updateMutate(data){
+ const UPDATE_DONE = gql`mutation updateTodoMutation($header_id: Int!, $id: Int!, $done: Boolean = false, $name: String = "", $updated: timestamp = "") {
+ update_todo_list_by_pk(pk_columns: {header_id: $header_id, id: $id}, _set: {updated: $updated, name: $name, done: $done}) {
+ id
+ header_id
+ }
+ }`
+ this.$refs.spinner.show()
+ this.$apollo.mutate({
+ mutation: UPDATE_DONE,
+ variables: {
+ header_id: data.header_id
+ , id: data.id
+ , done: data.done
+ , name: data.name
+ , updated: this.getNowFormatted()
+ }
+ })
+ .catch((error) => {
+ this.$refs.snackBar.setMessageAndshow('update error!')
+ console.error(error)
+ }).finally(() => {
+ this.$refs.spinner.close()
+ })
+ },
+ onDelete(data){
+ const DELETE_TODO = gql`mutation deleteTodoMutation($header_id: Int!, $id: Int!) {
+ delete_todo_list_by_pk(id: $id, header_id: $header_id) {
+ header_id
+ id
+ }
+ }`
+ this.$refs.spinner.show()
+ this.mutateApollo(DELETE_TODO, {
+ header_id: data.header_id
+ , id: data.id
+ }).catch((error) => {
+ this.$refs.snackBar.setMessageAndshow('delete error!')
+ console.error(error)
+ }).finally(() => {
+ this.$refs.spinner.close()
+ })
+ },
+ onAdd(){
+ const now = this.getNowFormatted()
+
+ if(this.selectedHeadrId === 0){
+ console.error("未選択")
+ return
+ }
+
+ const CREATE_TODO = gql`mutation createTodoMutation($header_id: Int!,
+ $id: Int!,
+ $done: Boolean = false,
+ $name: String = "",
+ $created: timestamp = "",
+ $updated: timestamp = "") {
+ insert_todo_list_one(object: {header_id: $header_id, id: $id, done: $done, name: $name, created: $created, updated: $updated}) {
+ header_id
+ id
+ done
+ name
+ created
+ updated
+ }
+ }`
+
+ this.$refs.spinner.show()
+ this.$apollo.queries.todo_list_aggregate.refetch({
+ selectedHeadrId: this.selectedHeadrId
+ }).then(() => {
+ return this.mutateApollo(CREATE_TODO, {
+ header_id:this.selectedHeadrId
+ , id:this.todo_list_aggregate.aggregate.max.id + 1
+ , done: false
+ , name: ""
+ , created: now
+ , updated: now
+ });
+ }).catch((error) => {
+ this.$refs.snackBar.setMessageAndshow('add error!')
+ console.error(error)
+ }).finally(() => {
+ this.$refs.spinner.close()
+ })
+ },
+ loading(){
+ return this.$apollo.queries.todo_list.loading
+ },
+ getNowFormatted(){
+ return this.formattedData()
+ },
+ formattedData(param){
+ return moment(param).format("YYYY-MM-DDTHH:mm:ss.SSS")
+ },
+ reFetch(titleId){
+ this.$refs.spinner.show()
+ this.$apollo.queries.todo_list.refetch({
+ selectedHeadrId: titleId
+ }).then(() => {
+ this.selectedHeadrId = titleId
+ }).finally(() => {
+ this.$refs.spinner.close()
+ })
+ },
+ mutateApollo(gqlMutation, variables){
+ const promise = this.$apollo.mutate({
+ mutation: gqlMutation,
+ variables: variables
+ })
+ .then(() => {
+ this.reFetch(this.selectedHeadrId)
+ return new Promise(resolve => {resolve()})
+ })
+ .catch((error) => {
+ return new Promise((resolve, reject) => {reject(error)})
+ })
+ return promise
+ },
+ },
+ data() {
+ return {
+ selectedHeadrId: 0,
+ titleName: '',
+ snackText: '',
+ }
+ },
+ apollo: {
+ // Simple query that will update the 'hello' vue property
+ todo_header: gql`query {
+ todo_header {
+ id
+ name
+ }
+ }`,
+ todo_list: gql`query ($selectedHeadrId: Int){
+ todo_list (where: {header_id: {_eq: $selectedHeadrId}}, order_by: {id: asc}) {
+ created
+ done
+ header_id
+ id
+ name
+ updated
+ }
+ }`,
+ todo_list_aggregate: gql`query ($selectedHeadrId: Int){
+ todo_list_aggregate(where: {header_id: {_eq: $selectedHeadrId}}) {
+ aggregate {
+ max {
+ id
+ }
+ }
+ }
+ }`,
+
+ },
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>