OSDN Git Service

ヘッダーの追加削除ができるようになった
[ngware/todo_client.git] / src / components / todoApp.vue
1 <template>
2   <div class="bg-gray-50">
3     <div class="text-center items-center justify-center">Todo GraphQl(Hasura)版</div>
4     <div class="max-w-xl mx-auto py-1 divide-y md:max-w-4xl">
5       <div class="py-10 px-5 rounded-md shadow-lg">
6         <div>Todo タイトル</div>
7         <input type="text" 
8               list="todo_header" 
9               v-model="titleName" 
10               @input="onHeaderChange"
11               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" />
12         <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"
13           @click="searchHeader">
14           <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-check" viewBox="0 0 16 16">
15             <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"/>
16           </svg>
17         </button>
18         <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"
19           v-if="selectedOption == null"
20           @click="addHeader">
21           <svg xmlns="http://www.w3.org/2000/svg" class="fill-current" viewBox="0 0 24 24">
22             <path d="M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z"/>
23           </svg>
24         </button>
25         <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"
26           v-else
27           @click="deleteHeader">
28           <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
29             <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"/>
30             <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"/>
31           </svg>
32         </button>
33         
34       </div>
35       <div class="py-10 px-5 rounded-md shadow-lg">
36         <div>Todo リスト</div>
37         <div class="py-1" v-for="list in todo_list" :key="list.id">
38           <todo :data="list"
39                 @onCheckChange="onCheckChange"
40                 @onTextChange="onTextChange"
41                 @onDelete="onDelete"/>
42         </div>
43         <div class="py-5">
44           <button
45                 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"
46                 @click="onAdd"
47                 v-if="selectedHeadrId != 0">
48             <svg viewBox="0 0 20 20" enable-background="new 0 0 20 20" class="w-6 h-6 inline-block">
49               <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
50                                       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
51                                       C15.952,9,16,9.447,16,10z" />
52             </svg>
53           </button>
54         </div>
55       </div>
56
57       <select-box/>
58       
59       <snack-bar :msg="snackText" ref="snackBar"/>
60       <select-panel 
61         :dataList="todo_header" 
62         title="Todoリスト選択" 
63         ref="selectPanel"
64         @onSelected="onSelected"/>
65
66       <spinner ref="spinner"/>
67
68     </div>
69   </div>
70 </template>
71
72 <script>
73
74 import moment from 'moment'
75 import _ from 'lodash'
76 //import { useQuery, useResult } from '@vue/apollo-composable' // eslint-disable-line no-unused-vars
77 import gql from 'graphql-tag'
78 import selectBox from '@/components/selectBox.vue'
79 import todo from '@/components/todo.vue'
80 import snackBar from '@/components/snackBar.vue'
81 import selectPanel from '@/components/selectPanel.vue'
82 import spinner from '@/components/spinner.vue'
83
84 export default {
85   name: 'todoApp',
86   components: {
87     selectBox
88     , todo
89     , snackBar
90     , selectPanel
91     , spinner
92   },
93   beforeMount() {
94     this.$apollo.queries.todo_list.refetch({
95         selectedHeadrId: 0
96     })
97   },
98   computed: {
99     // 選択中のオプションオブジェクト。任意入力値の場合は null を返す
100     selectedOption() {
101       if(!this.todo_header){
102         return null
103       }
104       const selected = this.todo_header.find(item => item.name === this.titleName)
105       return selected || null
106     }
107   },
108   //Vue2.6なのでsetupが…
109   // setup() {
110   //   // const { result, loading, error, refetch } = useQuery(gql`
111   //   //   query todo_header {
112   //   //     todo_header {
113   //   //       id
114   //   //       name
115   //   //     }
116   //   //   }
117   //   // `)
118
119   //   // const users = useResult(result)
120
121   //   // return {
122   //   //   users,
123   //   //   loading,
124   //   //   error,
125   //   //   refetch,
126   //   // }
127   //   onBeforeMount(() => {
128   //     console.log('Component is onBeforeMount!')
129   //     // this.$apollo.queries.todo_list.refetch({
130   //     //   selectedHeadrId: 0
131   //     // })
132   //   })
133   // },
134   methods: {
135     selectedTitle(id){
136       this.reFetch(id)
137     },
138     onSelected(head){
139       this.titleName = head.name
140       this.selectedTitle(head.id)
141     },
142     onHeaderChange() {
143       if(this.selectedOption){
144         this.selectedTitle(this.selectedOption.id)
145       } else {
146         this.$apollo.queries.todo_list.refetch({
147           selectedHeadrId: 0
148         })
149         this.selectedHeadrId = 0
150       }
151     },
152     addHeader(){
153       if(_.isEmpty(this.titleName)){
154         this.$refs.snackBar.setMessageAndshow('タイトルが入ってないよ!')
155         return
156       }
157       
158       const CREATE_TODO_HEAD = gql`mutation createHeader($id: Int!, $name: String = "") {
159                                 insert_todo_header_one(object: {id: $id, name: $name}) {
160                                   id
161                                   name
162                                 }
163                               }`
164
165       this.$refs.spinner.show()
166       this.$apollo.queries.todo_header_aggregate.refetch()
167       .then(() => {
168         this.selectedHeadrId = this.todo_header_aggregate.aggregate.max.id + 1
169         return this.mutateHead(CREATE_TODO_HEAD, {
170                         id: this.selectedHeadrId
171                         , name: this.titleName
172                       });
173       }).then(() => {
174         this.onHeaderChange()
175       }).catch((error) => {
176         this.$refs.snackBar.setMessageAndshow('add error!')
177         console.error(error)
178       }).finally(() => {
179         this.$refs.spinner.close()
180       })
181
182     },
183     deleteHeader(){
184       if(!_.isNumber(this.selectedHeadrId)){
185         this.$refs.snackBar.setMessageAndshow('タイトルが選ばれていないよ!')
186         return
187       }
188
189       const DELETE_TODO_HEAD = gql`mutation delete_todo_header($id: Int!) {
190                                     delete_todo_header_by_pk(id: $id) {
191                                       id
192                                     }
193                                   }`
194
195       this.$refs.spinner.show()
196       this.deleteTodoList(this.selectedHeadrId)
197       .then(() => {
198         return this.mutateHead(DELETE_TODO_HEAD, {id: this.selectedHeadrId})
199       }).then(() => {
200         this.selectedHeadrId = 0
201         this.titleName = ""
202       }).catch((error) => {
203         this.$refs.snackBar.setMessageAndshow('add error!')
204         console.error(error)
205       }).finally(() => {
206         this.$refs.spinner.close()
207       })
208     },
209     deleteTodoList(headerId){
210       const DELETE_TODO = gql`mutation deleteTitleMutate($header_id: Int!) {
211                                   delete_todo_list(where: {header_id: {_eq:$header_id}}) {
212                                     affected_rows
213                                   }
214                                 }`
215       const promise = this.mutateApollo(DELETE_TODO, {header_id: headerId})
216       .then(() => {
217         return new Promise(resolve => {resolve()})
218       }).catch((error) => {
219         return new Promise((resolve, reject) => {reject(error)})
220       })
221       return promise
222     },
223     mutateHead(gqlMutation, variables){
224       const promise = this.$apollo.mutate({
225             mutation: gqlMutation,
226             variables: variables
227           })
228       .then(() => {
229         return this.$apollo.queries.todo_header.refetch()
230       })
231       .catch((error) => {
232         return new Promise((resolve, reject) => {reject(error)})
233       })
234       return promise
235     },
236     searchHeader(){
237       this.$refs.spinner.show()
238       this.$apollo.queries.todo_header.refetch()
239       .then(() => {
240           this.$refs.selectPanel.show()
241       }).catch((error) => {
242         this.$refs.snackBar.setMessageAndshow('db error?')
243         console.error(error)
244       }).finally(() => {
245         this.$refs.spinner.close()
246       })
247       
248     },
249     onCheckChange(data) {
250       this.updateMutate(data);
251     },
252     onTextChange(data) {
253       this.updateMutate(data);
254     },
255     updateMutate(data){
256       const UPDATE_DONE = gql`mutation updateTodoMutation($header_id: Int!, $id: Int!, $done: Boolean = false, $name: String = "", $updated: timestamp = "") {
257                                         update_todo_list_by_pk(pk_columns: {header_id: $header_id, id: $id}, _set: {updated: $updated, name: $name, done: $done}) {
258                                           id
259                                           header_id
260                                         }
261                                       }`
262       this.$refs.spinner.show()
263       this.$apollo.mutate({
264             mutation: UPDATE_DONE,
265             variables: {
266                         header_id: data.header_id
267                         , id: data.id
268                         , done: data.done
269                         , name: data.name
270                         , updated: this.getNowFormatted()
271                       }
272           })
273       .catch((error) => {
274         this.$refs.snackBar.setMessageAndshow('update error!')
275         console.error(error)
276       }).finally(() => {
277         this.$refs.spinner.close()
278       })
279     },
280     onDelete(data){
281       const DELETE_TODO = gql`mutation deleteTodoMutation($header_id: Int!, $id: Int!) {
282                                         delete_todo_list_by_pk(id: $id, header_id: $header_id) {
283                                           header_id
284                                           id
285                                         }
286                                       }`
287       this.$refs.spinner.show()
288       this.mutateApollo(DELETE_TODO, {
289                         header_id: data.header_id
290                         , id: data.id
291        }).catch((error) => {
292         this.$refs.snackBar.setMessageAndshow('delete error!')
293         console.error(error)
294       }).finally(() => {
295         this.$refs.spinner.close()
296       })
297     },
298     onAdd(){
299       const now = this.getNowFormatted()
300
301       if(this.selectedHeadrId === 0){
302         console.error("未選択")
303         return
304       }
305       
306       const CREATE_TODO = gql`mutation createTodoMutation($header_id: Int!, 
307                                                   $id: Int!, 
308                                                   $done: Boolean = false, 
309                                                   $name: String = "", 
310                                                   $created: timestamp = "", 
311                                                   $updated: timestamp = "") {
312                                 insert_todo_list_one(object: {header_id: $header_id, id: $id, done: $done, name: $name, created: $created, updated: $updated}) {
313                                   header_id
314                                   id
315                                   done
316                                   name
317                                   created
318                                   updated
319                                 }
320                               }`
321
322       this.$refs.spinner.show()
323       this.$apollo.queries.todo_list_aggregate.refetch({
324           selectedHeadrId: this.selectedHeadrId
325       }).then(() => {
326         return this.mutateApollo(CREATE_TODO, {
327                         header_id:this.selectedHeadrId
328                         , id:this.todo_list_aggregate.aggregate.max.id + 1
329                         , done: false
330                         , name: ""
331                         , created: now
332                         , updated: now
333                       });
334       }).catch((error) => {
335         this.$refs.snackBar.setMessageAndshow('add error!')
336         console.error(error)
337       }).finally(() => {
338         this.$refs.spinner.close()
339       })
340     },
341     // loading(){
342     //   return this.$apollo.queries.todo_list.loading
343     // },
344     getNowFormatted(){
345       return this.formattedData()
346     },
347     formattedData(param){
348       return moment(param).format("YYYY-MM-DDTHH:mm:ss.SSS")
349     },
350     reFetch(titleId){
351       this.$refs.spinner.show()
352       this.$apollo.queries.todo_list.refetch({
353         selectedHeadrId: titleId
354       }).then(() => {
355         this.selectedHeadrId = titleId
356       }).finally(() => {
357         this.$refs.spinner.close()
358       })
359     },
360     mutateApollo(gqlMutation, variables){
361       const promise = this.$apollo.mutate({
362             mutation: gqlMutation,
363             variables: variables
364           })
365       .then(() => {
366         this.reFetch(this.selectedHeadrId)
367         return new Promise(resolve => {resolve()})
368       })
369       .catch((error) => {
370         return new Promise((resolve, reject) => {reject(error)})
371       })
372       return promise
373     },
374   },
375   data() {
376     return {
377       selectedHeadrId: 0,
378       titleName: '',
379       snackText: '',
380     }
381   },
382   apollo: {
383     // Simple query that will update the 'hello' vue property
384     todo_header: gql`query {
385         todo_header {
386         id
387         name
388       }
389     }`,
390     todo_list: gql`query ($selectedHeadrId: Int){
391         todo_list (where: {header_id: {_eq: $selectedHeadrId}}, order_by: {id: asc}) {
392           created
393           done
394           header_id
395           id
396           name
397           updated
398         }
399     }`,
400     todo_list_aggregate: gql`query ($selectedHeadrId: Int){
401         todo_list_aggregate(where: {header_id: {_eq: $selectedHeadrId}}) {
402           aggregate {
403             max {
404               id
405             }
406           }
407         }
408     }`,
409     todo_header_aggregate: gql`query {
410         todo_header_aggregate {
411           aggregate {
412             max {
413               id
414             }
415           }
416         }
417     }`,    
418   },
419 }
420 </script>
421
422 <!-- Add "scoped" attribute to limit CSS to this component only -->
423 <style scoped>
424 </style>