diff --git a/src/views/TrainerCreate.vue b/src/views/TrainerCreate.vue new file mode 100644 index 0000000000000000000000000000000000000000..8fbf9af34d8b9a4bcc8ae19f72bfdeb9bb6a1b90 --- /dev/null +++ b/src/views/TrainerCreate.vue @@ -0,0 +1,140 @@ +<template> + <div class="trainer-detail"> + <v-row> + <v-col cols="12"> + <v-card :loading="isProcessing"> + <v-card-title>트레이너 등록</v-card-title> + <v-card-text> + <v-alert + v-if="error.isError" + dense + text + type="error" + > + {{ error.message }} + </v-alert> + <v-text-field + label="이메일" + v-model="trainer.email" + ></v-text-field> + <v-text-field + label="비밀번호" + type="password" + v-model="trainer.password" + ></v-text-field> + <v-text-field + label="이름" + v-model="trainer.name" + ></v-text-field> + <v-text-field + label="닉네임" + v-model="trainer.nickname" + ></v-text-field> + <v-text-field + label="연락처" + v-model="trainer.phone" + ></v-text-field> + <v-menu + :close-on-content-click="false" + min-width="290px" + offset-y + v-model="showDatePicker" + > + <template v-slot:activator="{ on }"> + <v-text-field + label="생년월일" + readonly + v-model="trainer.birthDate" + v-on="on" + ></v-text-field> + </template> + <v-date-picker + v-model="trainer.birthDate" + @input="showDatePicker = false" + ></v-date-picker> + </v-menu> + <v-textarea + auto-grow + label="소개" + v-model="trainer.bio" + ></v-textarea> + </v-card-text> + <v-card-actions> + <v-spacer></v-spacer> + <v-btn + color="error" + outlined + @click="cancel" + > + <v-icon left>mdi-delete</v-icon> + 취소 + </v-btn> + <v-btn + color="primary" + outlined + @click="createTrainer" + > + <v-icon left>mdi-content-save</v-icon> + 저장 + </v-btn> + </v-card-actions> + </v-card> + </v-col> + </v-row> + </div> +</template> + +<script> +import APISetting from '@/settings/api'; + +export default { + name: 'TrainerCreate', + + data: () => ({ + isProcessing: false, + error: { + isError: false, + message: '', + }, + showDatePicker: false, + trainer: { + email: '', + password: '', + name: '', + nickname: '', + phone: '', + birthDate: '', + bio: '', + }, + }), + + methods: { + createTrainer() { + this.error.isError = false; + this.error.message = ''; + this.isProcessing = true; + + fetch(APISetting.endpoints.trainer.list, APISetting.settings.post(this.trainer)) + .then((res) => { + if ([201, 400, 500].includes(res.status)) return Promise.all([res.json(), res]); + throw new Error('알 수 없는 응답입니다.'); + }) + .then((values) => { + const [json, res] = values; + if (res.status !== 201) throw new Error(json.message); + this.$router.push('/trainer'); + }) + .catch((e) => { + this.error.message = e.message; + this.error.isError = true; + }) + .finally(() => { + this.isProcessing = false; + }); + }, + cancel() { + this.$router.push('/trainer'); + }, + }, +}; +</script> diff --git a/src/views/TrainerDetail.vue b/src/views/TrainerDetail.vue index d1248bf2f8ff7b6cbeb1e9b3e3506f45a8e810c9..d76de2aa4f1e2a958f61d9d54bc7a352a031a55b 100644 --- a/src/views/TrainerDetail.vue +++ b/src/views/TrainerDetail.vue @@ -1,88 +1,102 @@ <template> <div class="trainer-detail"> - <v-card - :loading="isProcessing" - > - <v-card-title>트레이너 정보</v-card-title> - <v-card-text> - <v-alert - v-if="error.isError" - dense - text - type="error" - > - {{ error.message }} - </v-alert> - <v-text-field - label="등록번호" - readonly - v-model="id" - ></v-text-field> - <v-text-field - label="이름" - v-model="trainer.name" - ></v-text-field> - <v-text-field - label="이메일" - v-model="trainer.email" - ></v-text-field> - <v-text-field - label="연락처" - v-model="trainer.phone" - ></v-text-field> - <v-menu - :close-on-content-click="false" - min-width="290px" - offset-y - v-model="showDatePicker" - > - <template - v-slot:activator="{ on }" - > + <v-row> + <v-col cols="12"> + <v-card :loading="isProcessing"> + <v-card-title>트레이너 정보</v-card-title> + <v-card-text> + <v-alert + v-if="error.isError" + dense + text + type="error" + > + {{ error.message }} + </v-alert> <v-text-field - label="생년월일" + label="등록번호" readonly - v-model="trainer.birthDate" - v-on="on" + v-model="id" ></v-text-field> - </template> - <v-date-picker - v-model="trainer.birthDate" - @input="showDatePicker = false" - ></v-date-picker> - </v-menu> - <v-text-field - label="등록 시각" - readonly - v-model="trainer.createdAt" - ></v-text-field> - <v-text-field - label="최종 수정 시각" - readonly - v-model="trainer.updatedAt" - ></v-text-field> - </v-card-text> - <v-card-actions> - <v-btn - color="error" - outlined - @click="deleteTrainer(id)" - > - 삭제 - </v-btn> - <v-btn - color="primary" - outlined - @click="updateTrainer(id)" - > - 저장 - </v-btn> - </v-card-actions> - </v-card> + <v-text-field + label="이메일" + v-model="trainer.email" + ></v-text-field> + <v-text-field + label="이름" + v-model="trainer.name" + ></v-text-field> + <v-text-field + label="닉네임" + v-model="trainer.nickname" + ></v-text-field> + <v-text-field + label="연락처" + v-model="trainer.phone" + ></v-text-field> + <v-menu + :close-on-content-click="false" + min-width="290px" + offset-y + v-model="showDatePicker" + > + <template v-slot:activator="{ on }"> + <v-text-field + label="생년월일" + readonly + v-model="trainer.birthDate" + v-on="on" + ></v-text-field> + </template> + <v-date-picker + v-model="trainer.birthDate" + @input="showDatePicker = false" + ></v-date-picker> + </v-menu> + <v-textarea + auto-grow + label="소개" + v-model="trainer.bio" + ></v-textarea> + <v-text-field + label="등록 시각" + readonly + v-model="trainer.createdAt" + ></v-text-field> + <v-text-field + label="최종 수정 시각" + readonly + v-model="trainer.updatedAt" + ></v-text-field> + </v-card-text> + <v-card-actions> + <v-spacer></v-spacer> + <v-btn + color="error" + outlined + @click="deleteTrainer(id)" + > + <v-icon left>mdi-delete</v-icon> + 삭제 + </v-btn> + <v-btn + color="primary" + outlined + @click="updateTrainer(id)" + > + <v-icon left>mdi-content-save</v-icon> + 저장 + </v-btn> + </v-card-actions> + </v-card> + </v-col> + </v-row> </div> </template> <script> +import moment from 'moment'; + import APISetting from '@/settings/api'; export default { @@ -102,8 +116,8 @@ export default { phone: '', birthDate: '', bio: '', - updatedAt: '', createdAt: '', + updatedAt: '', }, }), @@ -130,6 +144,9 @@ export default { const [json, res] = values; if (res.status === 404) throw new Error('존재하지 않는 데이터입니다.'); if (res.status !== 200) throw new Error(json.message); + const { trainer } = json; + trainer.createdAt = moment(trainer.createdAt).format('YYYY-MM-DD HH:mm:ss'); + trainer.updatedAt = moment(trainer.updatedAt).format('YYYY-MM-DD HH:mm:ss'); this.trainer = json.trainer; }) .catch((e) => { @@ -148,13 +165,16 @@ export default { fetch(APISetting.endpoints.trainer.detail(id), APISetting.settings.put(this.trainer)) .then((res) => { if (res.status === 404) return Promise.all([null, res]); - if ([200, 400, 404, 500].includes(res.status)) return Promise.all([res.json(), res]); + if ([200, 400, 500].includes(res.status)) return Promise.all([res.json(), res]); throw new Error('알 수 없는 응답입니다.'); }) .then((values) => { const [json, res] = values; if (res.status === 404) throw new Error('존재하지 않는 데이터입니다.'); if (res.status !== 200) throw new Error(json.message); + const { trainer } = json; + trainer.createdAt = moment(trainer.createdAt).format('YYYY-MM-DD HH:mm:ss'); + trainer.updatedAt = moment(trainer.updatedAt).format('YYYY-MM-DD HH:mm:ss'); this.trainer = json.trainer; }) .catch((e) => { @@ -173,8 +193,7 @@ export default { fetch(APISetting.endpoints.trainer.detail(id), APISetting.settings.delete) .then((res) => { if ([204, 404].includes(res.status)) return Promise.all([null, res]); - if ([204, 400, 404, 500].includes(res.status)) return Promise.all([res.json(), res]); - // If response status is not equal to 204, 400, 404, or 500, go to catch. + if ([400, 500].includes(res.status)) return Promise.all([res.json(), res]); throw new Error('알 수 없는 응답입니다.'); }) .then((values) => { diff --git a/src/views/TrainerList.vue b/src/views/TrainerList.vue index 9fa71204f395da080d4050aba67dada587d349ad..0142173224a5c8c14c73d4c73d9f7d4afe06f557 100644 --- a/src/views/TrainerList.vue +++ b/src/views/TrainerList.vue @@ -1,57 +1,78 @@ <template> <div class="trainer-list"> - <v-card> - <v-card-title>트레이너 목록</v-card-title> - <v-card-text> - <v-row - justify="end" - > - <v-col - cols="12" - sm="4" - md="3" - lg="3" - xl="2" - > - <v-text-field - append-icon="mdi-magnify" - clearable - label="트레이너 검색" - v-model="trainerList.searchKeyword" - ></v-text-field> - </v-col> - </v-row> - <v-data-table - :headers="trainerList.headers" - item-key="id" - :items="trainerList.data" - :loading="isProcessing" - loading-text="데이터를 불러오는 중입니다." - :search="trainerList.searchKeyword" - no-results-text="일치하는 트레이너를 찾지 못했습니다." - > - <template v-slot:item.action="{ item }"> - <v-icon - small - class="mr-2" - @click="editTrainer(item.id)" + <v-row> + <v-col cols="12"> + <v-card> + <v-card-title>트레이너 목록</v-card-title> + <v-card-text> + <v-row> + <v-col + align-self="center" + cols="12" + sm="8" + md="9" + lg="9" + xl="10" + > + <v-btn + color="success" + outlined + @click="createTrainer" + > + <v-icon left>mdi-plus</v-icon> + 새 트레이너 등록 + </v-btn> + </v-col> + <v-col + cols="12" + sm="4" + md="3" + lg="3" + xl="2" + > + <v-text-field + append-icon="mdi-magnify" + clearable + label="트레이너 검색" + v-model="trainerList.searchKeyword" + ></v-text-field> + </v-col> + </v-row> + <v-data-table + :headers="trainerList.headers" + item-key="id" + :items="trainerList.data" + :loading="isProcessing" + loading-text="데이터를 불러오는 중입니다." + :search="trainerList.searchKeyword" + no-results-text="일치하는 트레이너를 찾지 못했습니다." > - mdi-pencil - </v-icon> - <v-icon - small - @click="deleteTrainer(item.id)" - > - mdi-delete - </v-icon> - </template> - </v-data-table> - </v-card-text> - </v-card> + <template v-slot:item.action="{ item }"> + <v-icon + small + class="mr-2" + @click="editTrainer(item.id)" + > + mdi-pencil + </v-icon> + <v-icon + small + @click="deleteTrainer(item.id)" + > + mdi-delete + </v-icon> + </template> + </v-data-table> + </v-card-text> + </v-card> + </v-col> + </v-row> </div> </template> <script> +import moment from 'moment'; + import APISetting from '@/settings/api'; export default { @@ -86,8 +107,8 @@ export default { value: 'phone', }, { - text: '소개', - value: 'bio', + text: '등록 시각', + value: 'createdAt', }, { text: '', @@ -109,11 +130,15 @@ export default { fetch(APISetting.endpoints.trainer.list, APISetting.settings.get) .then((res) => { if (res.status === 200) return res.json(); - // If response status is not equal to 200, go to catch. throw new Error('알 수 없는 응답입니다.'); }) .then((json) => { - this.trainerList.data = json.trainers; + const { trainers } = json; + trainers.forEach((trainer) => { + // eslint-disable-next-line no-param-reassign + trainer.createdAt = moment(trainer.createdAt).format('YYYY-MM-DD HH:mm:ss'); + }); + this.trainerList.data = trainers; }) .catch((e) => { this.error.message = e.message; @@ -123,31 +148,34 @@ export default { this.isProcessing = false; }); }, + createTrainer() { + this.$router.push('/trainer/create'); + }, editTrainer(id) { this.$router.push(`/trainer/${id}`); }, deleteTrainer(id) { this.error.isError = false; this.error.message = ''; + this.isProcessing = true; fetch(APISetting.endpoints.trainer.detail(id), APISetting.settings.delete) .then((res) => { if (res.status === 204) return Promise.all([null, res]); - if ([204, 400, 404, 500].includes(res.status)) return Promise.all([res.json(), res]); - // If response status is not equal to 204, 400, 404, 500, go to catch. + if ([400, 404, 500].includes(res.status)) return Promise.all([res.json(), res]); throw new Error('알 수 없는 응답입니다.'); }) .then((values) => { const [json, res] = values; - // If trainer successfully deleted and response status is equal to 204, - // update trainer list. if (res.status === 204) return this.getTrainerList(); - // Otherwise, go to catch. throw new Error(json.message); }) .catch((e) => { this.error.message = e.message; this.error.isError = true; + }) + .finally(() => { + this.isProcessing = false; }); }, },