Merge branch 'Phase3' of ssh://dev.pollex.com.tw:29418/pcalife/PAM into Phase3
| | |
| | | <div class="pam-link-button--lg" |
| | | @click="addInterview">+æ°å¢</div> |
| | | </div> |
| | | <InterviewCard :interviewList="displayList.slice(0, 3)"></InterviewCard> |
| | | |
| | | <template v-if="!interviewList.length"> |
| | | <div class="record-card record-card--empty"> |
| | | ç¡ç´è¨ªç´é |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-if="interviewList.length"> |
| | | <div |
| | | v-for="(item, index) in futureList" |
| | | :key="index + 'feature'" |
| | | class="interview--future" |
| | | @click="editInterview(item)" |
| | | > |
| | | |
| | | <div class="record-card"> |
| | | <div class="record-card-date"> |
| | | <div> |
| | | <UiDateFormat |
| | | class="date bold" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="time mt-5 line-space" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | <div class="record-card-content"> |
| | | <span>{{item.content}}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <section |
| | | class="interview--past" |
| | | v-for="(item, index) in pastList" |
| | | :key="index + 'past'" |
| | | @click="editInterview(item)" |
| | | > |
| | | <div class="record-card"> |
| | | <div class="record-card-date"> |
| | | <div> |
| | | <UiDateFormat |
| | | class="date bold" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="time mt-5 line-space" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | <div class="record-card-content"> |
| | | <span>{{item.content}}</span> |
| | | </div> |
| | | </div> |
| | | <section class="text--right mt-30" v-if="interviewList.length > 3"> |
| | | <div class="pam-link-button--lg" @click="readMoreBtn">å±éçæ´å¤</div> |
| | | </section> |
| | | |
| | | <section class="more-log-action"> |
| | | <div class="pam-link-button--lg">å±éçæ´å¤</div> |
| | | </section> |
| | | </template> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Vue, Component, Prop, Watch, Mutation } from 'nuxt-property-decorator'; |
| | | import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator'; |
| | | import { InterviewRecord } from '~/shared/models/appointment.model'; |
| | | |
| | | @Component |
| | |
| | | @Prop() |
| | | interviewList!: InterviewRecord[]; |
| | | |
| | | @Mutation |
| | | updateInterviewRecord!: (data: InterviewRecord) => void; |
| | | |
| | | appointmentId!: string; |
| | | |
| | | futureList: InterviewRecord[] = []; |
| | | pastList: InterviewRecord[] = []; |
| | | displayList: InterviewRecord[] = []; |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | |
| | | @Watch('interviewList', {immediate: true}) |
| | | updateInterviewList() { |
| | | if (this.interviewList && this.interviewList.length > 0) { |
| | | this.futureList = this.interviewList |
| | | .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime()) |
| | | this.pastList = this.interviewList |
| | | .filter(item => new Date(item.interviewDate).getTime() < new Date().getTime()); |
| | | this.displayList = this.interviewList |
| | | .map((i) => ({ ...i, sortDate: new Date(i.interviewDate)})) |
| | | .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate); |
| | | } |
| | | } |
| | | |
| | |
| | | this.$router.push(`/appointment/${this.appointmentId}/interview/new`); |
| | | } |
| | | |
| | | editInterview(interviewRecord) { |
| | | this.updateInterviewRecord(interviewRecord); |
| | | this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`); |
| | | readMoreBtn() { |
| | | this.$router.push(`/appointment/${this.appointmentId}/interviewList`); |
| | | } |
| | | |
| | | } |
| | |
| | | display : flex; |
| | | justify-content: space-between; |
| | | margin-bottom : 10px; |
| | | } |
| | | .interview--future{ |
| | | border-bottom: 1px solid #CCCCCC; |
| | | padding-bottom: 17px; |
| | | margin-bottom: 17px; |
| | | .record{ |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | .record-card { |
| | | height: 62px; |
| | | border: 1px solid #707070; |
| | | border-radius: 5px; |
| | | display: flex; |
| | | border-bottom: 1px solid #000; |
| | | .record-card-date{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | margin-left: 10px; |
| | | margin-right: 10px; |
| | | margin-top: 10px; |
| | | } |
| | | .record-card-content{ |
| | | height: 42px; |
| | | margin-top: 10px; |
| | | margin-right: 10px; |
| | | line-height: 1.2; |
| | | } |
| | | &.record-card--empty { |
| | | align-items : center; |
| | | background-color: #fff; |
| | | color : $MID_GREY; |
| | | justify-content : center; |
| | | } |
| | | } |
| | | .line-space{ |
| | | letter-spacing: 1px; |
| | | } |
| | | .more-log-action{ |
| | | margin-top: 30px; |
| | | display: flex; |
| | | justify-content:flex-end; |
| | | } |
| | | </style> |
| | |
| | | <div class="record-log-component"> |
| | | <div class="mdTxt mt-30 mb-10">系統éç¥ç´é</div> |
| | | |
| | | <div v-for="(item, index) in displayLogs" |
| | | :key="index"> |
| | | <section |
| | | class="record-log-card" |
| | | > |
| | | <div class="record-log-card-date-container"> |
| | | <div class="record-log-card-date-container-circle"> |
| | | <div class="xxsTxt bold line-height">{{item.createdDate | formatYear}}</div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="xxsTxt bold line-height" |
| | | :date="item.createdDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="xxsTxt mt-4 line-space" |
| | | :date="item.createdDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="record-log-msg"> |
| | | <div>ç¼éç´è¨ªéç¥ |
| | | <span v-if="item.email && item.phone">(ææ©ç°¡è¨ãEmail)</span> |
| | | <span v-else-if="item.email">(Email)</span> |
| | | <span v-else>(ææ©ç°¡è¨)</span> |
| | | </div> |
| | | <div class="mt-10">é ç´{{item.interviewDate | formatDate}}</div> |
| | | </div> |
| | | </section> |
| | | <div class="time-line"></div> |
| | | </div> |
| | | <InterviewRecordCard :noticeLogsList="displayLogs.slice(0, 3)"></InterviewRecordCard> |
| | | |
| | | <section class="more-log-action"> |
| | | <section class="text--right mt-30" v-if="displayLogs.length > 3"> |
| | | <div class="pam-link-button--lg" |
| | | @click="readMoreBtn" |
| | | >å±éçæ´å¤</div> |
| | | </section> |
| | | </div> |
| | |
| | | import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator'; |
| | | import { NoticeLogs } from '~/shared/models/appointment.model'; |
| | | |
| | | @Component({ |
| | | filters: { |
| | | formatYear(value) { |
| | | if (value) { |
| | | return new Date(value).getFullYear(); |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | @Component |
| | | export default class AppointmentRecordList extends Vue { |
| | | |
| | | @Prop() |
| | |
| | | |
| | | appointmentId: string = ''; |
| | | displayLogs : NoticeLogs[] = []; |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | mounted() { |
| | | this.appointmentId = this.$route.params.appointmentId; |
| | |
| | | } |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | readMoreBtn() { |
| | | this.$router.push(`/appointment/${this.appointmentId}/recordList`); |
| | | } |
| | | |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .record-log-component{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | .record-log-card{ |
| | | display: flex; |
| | | .record-log-card-date-container{ |
| | | position:relative; |
| | | .record-log-card-date-container-circle{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 50%; |
| | | border:1px solid $PRIMARY_BLACK; |
| | | justify-content: center; |
| | | align-items: center; |
| | | align-content: center; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .mt-4{ |
| | | margin-top: 4px; |
| | | } |
| | | .line-space{ |
| | | letter-spacing: 1px; |
| | | } |
| | | .line-height{ |
| | | line-height:1.2; |
| | | } |
| | | .time-line{ |
| | | border-left: 1px solid black; |
| | | height: 30px; |
| | | margin-left: 28px; |
| | | |
| | | } |
| | | .record-log-msg{ |
| | | margin-left: 13px; |
| | | margin-top: 10px; |
| | | } |
| | | .more-log-action{ |
| | | display: flex; |
| | | justify-content:flex-end; |
| | | } |
| | | |
| | | </style> |
| | |
| | | import * as _ from 'lodash'; |
| | | import { Role } from '~/shared/models/enum/Role'; |
| | | |
| | | const appointmentStore = namespace('appointment.store'); |
| | | const roleStorage = namespace('localStorage'); |
| | | |
| | | @Component |
| | |
| | | @roleStorage.Getter |
| | | currentRole!:string; |
| | | |
| | | @appointmentStore.Getter |
| | | isCloseAppointment!: boolean; |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | goBack(): void { |
| | | const pathName = this.$route.name; |
| | | pathName?.includes('myConsultantList') ? this.$router.push('/') : this.$router.go(-1); |
| | | pathName?.includes('myConsultantList') |
| | | ? this.$router.push('/') |
| | | : this.$router.go(-1); |
| | | } |
| | | |
| | | get label(): string { |
| | |
| | | featureLabel = 'F&Q 常è¦åé¡'; |
| | | break; |
| | | case 'appointment': |
| | | const appointmentFeatureLabel = this.$route.name.includes('close') ? 'çµæ¡' : 'é ç´è³è¨'; |
| | | const appointmentFeatureLabel = this.$route.name.includes('close') |
| | | ? 'çµæ¡' |
| | | : this.isCloseAppointment ? 'çµæ¡æç´°' : 'é ç´è³è¨'; |
| | | const inInterview = this.$route.name.includes('interview'); |
| | | const addNewInterview = this.$route.name.includes('new'); |
| | | if (inInterview) { |
| | | featureLabel = addNewInterview ? 'æ°å¢ç´è¨ªç´é' : '編輯ç´è¨ªç´é'; |
| | | const interviewList = this.$route.name.includes('interviewList'); |
| | | const recordList = this.$route.name.includes('recordList'); |
| | | if (interviewList) { |
| | | featureLabel = 'ç´è¨ªç´é'; |
| | | } else if (recordList) { |
| | | featureLabel = '系統éç¥ç´é'; |
| | | } else if (inInterview) { |
| | | featureLabel = addNewInterview |
| | | ? 'æ°å¢ç´è¨ªç´é' |
| | | : '編輯ç´è¨ªç´é'; |
| | | } else { |
| | | featureLabel = appointmentFeatureLabel; |
| | | } |
| | |
| | | updateMyAppointmentList!: (data: Appointment) => void; |
| | | |
| | | @appointmentStore.Action |
| | | setAppointmentDetail!: (appointmentId: number) => Promise<Appointment>; |
| | | getAppointmentDetail!: (appointmentId: number) => Promise<Appointment>; |
| | | |
| | | @appointmentStore.Getter |
| | | appointmentProgress!: ContactStatus; |
| | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | viewAppointmentDetail(): void { |
| | | this.setAppointmentDetail(this.client.id).then((_) => { |
| | | this.getAppointmentDetail(this.client.id).then((_) => { |
| | | this.$router.push(`/appointment/${this.client.id}`); |
| | | }); |
| | | } |
| | |
| | | <template> |
| | | <div class="edit-appointment-record"> |
| | | <div class="edit-appointment-record-date" v-if="interviewId"> |
| | | <div class="edit-appointment-record-date" v-if="interviewId && interviewRecord"> |
| | | <span>{{interviewRecord.createdDate | formatDate}} 建ç«</span> |
| | | <span>{{interviewRecord.lastModifiedDate | formatDate}} æ´æ°</span> |
| | | </div> |
| | |
| | | |
| | | <InterviewMsg |
| | | :isVisible.sync="showInterviewMsgPopup" |
| | | :client="appointmentDetail" |
| | | @closeDialog="closePopup" |
| | | ></InterviewMsg> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model'; |
| | | import { Vue, Component, Prop, State, Mutation, Watch, Action } from 'nuxt-property-decorator'; |
| | | import { Appointment, InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model'; |
| | | import { Vue, Component, Watch, namespace } from 'nuxt-property-decorator'; |
| | | import appointmentService from '~/shared/services/appointment.service'; |
| | | |
| | | const appointmentStore = namespace('appointment.store'); |
| | | |
| | | @Component |
| | | export default class InterviewAdd extends Vue { |
| | | @State |
| | | interviewRecord!: InterviewRecord; |
| | | |
| | | @Mutation |
| | | updateInterviewRecord!: (data: InterviewRecord) => void; |
| | | @appointmentStore.State |
| | | appointmentDetail!: Appointment; |
| | | |
| | | @Mutation |
| | | clearInterviewRecord!: () => void; |
| | | @appointmentStore.Action |
| | | updateAppointmentDetail!: (id: number) => Appointment; |
| | | |
| | | interviewTime = ''; |
| | | content = ''; |
| | |
| | | |
| | | defaultValue = ''; |
| | | |
| | | interviewRecord!: InterviewRecord; |
| | | |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | mounted() { |
| | | this.interviewId = this.$route.params.interviewId; |
| | | this.appointmentId = this.$route.params.appointmentId; |
| | | const isEditPage = this.interviewId && this.interviewRecord; |
| | | if (isEditPage) { |
| | | this.checkInterviewRecord(); |
| | | } |
| | | } |
| | | |
| | | private checkInterviewRecord() { |
| | | if (this.interviewRecord.appointmentId !== +this.appointmentId |
| | | || this.interviewRecord.id !== +this.interviewId) { |
| | | appointmentService.getAppointmentDetail(+this.appointmentId).then((data) => { |
| | | const currentInterviewRecord = data.interviewRecordDTOs.filter(item => item.id === +this.interviewId)[0]; |
| | | this.updateInterviewRecord(currentInterviewRecord); |
| | | }) |
| | | } |
| | | } |
| | | |
| | | destroyed() { |
| | | this.clearInterviewRecord(); |
| | | this.onAppointmentDetailChange(); |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | @Watch('interviewRecord', {immediate: true}) |
| | | watchInterviewRecord() { |
| | | if (this.interviewRecord && this.interviewRecord.content) { |
| | | @Watch('appointmentDetail', {immediate: true}) |
| | | onAppointmentDetailChange() { |
| | | if (this.appointmentDetail && this.appointmentDetail.id === +this.appointmentId) { |
| | | this.interviewRecord = this.appointmentDetail.interviewRecordDTOs |
| | | .filter(item => item.id === +this.interviewId)[0]; |
| | | |
| | | if (this.interviewRecord && this.interviewId) { |
| | | this.content = this.interviewRecord.content; |
| | | this.defaultValue = this.interviewRecord.interviewDate; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | this.updateRecord(updateInterviewRecord); |
| | | } |
| | | this.updateAppointmentDetail(+this.appointmentId); |
| | | } |
| | | |
| | | private createdRecord(interviewRecordInfo) { |
| | |
| | | } |
| | | } |
| | | |
| | | closePopup() { |
| | | this.$router.push(`/appointment/${this.appointmentId}`); |
| | | } |
| | | |
| | | deleteInterviewRecord() { |
| | | appointmentService.deleteInterviewRecord(this.interviewId).then(res => { |
| | | this.confirmTxt = 'åªé¤æå'; |
| | | this.showConfirmPopup = true; |
| | | this.updateAppointmentDetail(+this.appointmentId); |
| | | }); |
| | | } |
| | | |
| | |
| | | this.defaultValue = this.interviewRecord.interviewDate; |
| | | this.isEdit = false; |
| | | } else { |
| | | this.$router.push(`/appointment/${this.appointmentId}`); |
| | | this.$router.go(-1); |
| | | } |
| | | } |
| | | |
| | | closePopup() { |
| | | this.$router.go(-1); |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | get formatInterviewDate() { |
¤ñ¹ï·sÀÉ®× |
| | |
| | | <template> |
| | | <div> |
| | | <template v-if="!interviewList.length"> |
| | | <div class="record-card record-card--empty"> |
| | | ç¡ç´è¨ªç´é |
| | | </div> |
| | | </template> |
| | | |
| | | <template v-else> |
| | | <div class="interview--future"> |
| | | <div class="record-card mb-10" |
| | | v-for="(item, index) in futureList" |
| | | :key="index + 'feature'" |
| | | @click="editInterview(item)" |
| | | > |
| | | <div class="record-card-date"> |
| | | <div> |
| | | <UiDateFormat |
| | | class="date bold" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="time mt-5 line-space" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | <div class="record-card-content"> |
| | | <span>{{item.content}}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <section class="interview--past" v-if="pastList.length"> |
| | | <div class="record-card mb-10" |
| | | v-for="(item, index) in pastList" |
| | | :key="index + 'past'" |
| | | @click="editInterview(item)" |
| | | > |
| | | <div class="record-card-date"> |
| | | <div> |
| | | <UiDateFormat |
| | | class="date bold" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="time mt-5 line-space" |
| | | :date="item.interviewDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | <div class="record-card-content"> |
| | | <span>{{item.content}}</span> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | </template> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Component, Prop, Vue, Watch } from "nuxt-property-decorator"; |
| | | import { InterviewRecord } from "~/shared/models/appointment.model"; |
| | | |
| | | @Component |
| | | export default class InterviewCard extends Vue { |
| | | |
| | | @Prop() |
| | | interviewList!: InterviewRecord[]; |
| | | |
| | | futureList: InterviewRecord[] = []; |
| | | pastList: InterviewRecord[] = []; |
| | | |
| | | appointmentId!: number; |
| | | |
| | | mounted() { |
| | | this.appointmentId = +this.$route.params.appointmentId; |
| | | } |
| | | |
| | | @Watch('interviewList', {immediate: true}) |
| | | onInterviewListChange() { |
| | | if (this.interviewList.length > 0) { |
| | | this.futureList = this.interviewList |
| | | .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime()) |
| | | .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate)); |
| | | this.pastList = this.interviewList |
| | | .filter(item => new Date(item.interviewDate).getTime() < new Date().getTime()) |
| | | .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate)); |
| | | } |
| | | |
| | | } |
| | | |
| | | editInterview(interviewRecord) { |
| | | this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .interview--future{ |
| | | .record{ |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | .interview--past { |
| | | border-top: 1px solid #CCCCCC; |
| | | padding-top: 17px; |
| | | margin-top: 17px; |
| | | } |
| | | .record-card { |
| | | height: 62px; |
| | | border: 1px solid #707070; |
| | | border-radius: 5px; |
| | | display: flex; |
| | | border-bottom: 1px solid #000; |
| | | .record-card-date{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | margin-left: 10px; |
| | | margin-right: 10px; |
| | | margin-top: 10px; |
| | | } |
| | | .record-card-content{ |
| | | height: 42px; |
| | | margin-top: 10px; |
| | | margin-right: 10px; |
| | | line-height: 1.2; |
| | | } |
| | | &.record-card--empty { |
| | | align-items : center; |
| | | background-color: #fff; |
| | | color : $MID_GREY; |
| | | justify-content : center; |
| | | } |
| | | } |
| | | .line-space{ |
| | | letter-spacing: 1px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="interview-msg-component"> |
| | | <el-dialog |
| | | :visible.sync="dialogVisible" |
| | | :width="dialogWidth" |
| | |
| | | |
| | | <el-input |
| | | type="textarea" |
| | | :rows="9" |
| | | :autosize="true" |
| | | placeholder="ç´è¨ªéç¥" |
| | | resize="none" |
| | | v-model="interviewTxt"> |
| | | v-model="isInterviewTxt"> |
| | | </el-input> |
| | | |
| | | <div class="mdTxt mt-30 mb-10">é è¨ç´è¨ªææ®µ</div> |
| | |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { Vue, Component, Prop, PropSync, Emit, namespace } from 'nuxt-property-decorator'; |
| | | import { Vue, Component, Prop, PropSync, Emit, Action, namespace } from 'nuxt-property-decorator'; |
| | | |
| | | import appointmentService from '~/shared/services/appointment.service'; |
| | | import { Appointment, ToInformAppointment } from '~/shared/models/appointment.model'; |
| | | import { ContactStatus } from '~/shared/models/enum/contact-status'; |
| | | import { AgentInfo } from '~/shared/models/agent-info.model'; |
| | | |
| | | const loginStore = namespace('login.store'); |
| | | const appointmentStore = namespace('appointment.store'); |
| | | |
| | | @Component |
| | | export default class InterviewMsg extends Vue { |
| | | |
| | | @appointmentStore.Action |
| | | getMyAppointmentList!: () => Promise<Appointment[]>; |
| | | |
| | | @Action |
| | | storeMyAppointmentList!: () => Promise<number>; |
| | | |
| | | @appointmentStore.Action |
| | | updateMyAppointmentList!:(appointment: Appointment) => Appointment[]; |
| | | |
| | | @appointmentStore.Action |
| | | updateAppointmentDetail!: (appointmentId: number) => Promise<Appointment>; |
| | | |
| | | @appointmentStore.State |
| | | appointmentDetail!: Appointment; |
| | | updateAppointmentDetail!: (id: number) => Appointment; |
| | | |
| | | @PropSync('isVisible') |
| | | dialogVisible!: boolean; |
| | |
| | | return; |
| | | } |
| | | |
| | | interviewTime = ''; |
| | | interviewTxt = ''; |
| | | @loginStore.State |
| | | loginConsultant!: AgentInfo; |
| | | |
| | | isShowSuccessAlert = false; |
| | | |
| | | contactStatus = ContactStatus; |
| | | |
| | | interviewTxt = ""; |
| | | interviewTime = ''; |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | addInterview() { |
| | |
| | | message : this.interviewTxt, |
| | | phone : this.client?.phone, |
| | | }; |
| | | |
| | | appointmentService.informAppointment(appointmentInformation).then((_) => { |
| | | this.isShowSuccessAlert = true ; |
| | | const updatedAppointment = { |
| | | ...this.appointmentDetail, |
| | | communicateStatus: this.contactStatus.CONTACTED, |
| | | }; |
| | | this.updateMyAppointmentList(updatedAppointment); |
| | | this.updateAppointmentDetail(updatedAppointment.id); |
| | | this.updateAppointmentDetail(this.client.id); |
| | | }); |
| | | } |
| | | |
| | | closeAllDialog() { |
| | | this.isShowSuccessAlert = false ; |
| | | this.dialogVisible = false; |
| | | this.getMyAppointmentList(); |
| | | this.storeMyAppointmentList(); |
| | | } |
| | | |
| | | get isInterviewTxt() : string{ |
| | | return this.interviewTxt = "æ¨å¥½ï¼ææ¯ä¿èª åªåå¹³å°çä¿éªé¡§å" + this.loginConsultant?.name + "ï¼æè¬æ¨çé ç´ï¼æé è¨æå¨ä¸è¿°çæéèæ¨è¯ç¹«"+"\n"+"以䏿¯æçé»è©±è碼/Emailï¼"+"\n" + this.loginConsultant?.phoneNumber + "\n" + this.loginConsultant?.email + "\n"+"è¥æ¤æé䏿¹ä¾¿ï¼è«èæè¯ç¹«ï¼è¬è¬ï¼" |
| | | } |
| | | |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | <style lang="scss" > |
| | | .interview-msg-component{ |
| | | |
| | | .msg-dialog-title{ |
| | | display: flex; |
| | | justify-content: center; |
| | |
| | | font-size: 20px; |
| | | padding:10px; |
| | | text-align: justify; |
| | | font-weight: 500; |
| | | font-weight: 600; |
| | | } |
| | | .msg-dialog-btn{ |
| | | margin-top: 30px; |
| | |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | } |
| | | </style> |
¤ñ¹ï·sÀÉ®× |
| | |
| | | <template> |
| | | <div class="record-log-component"> |
| | | <div v-for="(item, index) in noticeLogsList" |
| | | :key="index"> |
| | | <section |
| | | class="record-log-card" |
| | | > |
| | | <div class="record-log-card-date-container"> |
| | | <div class="record-log-card-date-container-circle"> |
| | | <div class="xxsTxt bold line-height">{{item.createdDate | formatYear}}</div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="xxsTxt bold line-height" |
| | | :date="item.createdDate" |
| | | onlyShowSection="DAY" /> |
| | | </div> |
| | | <div> |
| | | <UiDateFormat |
| | | class="xxsTxt mt-4 line-space" |
| | | :date="item.createdDate" |
| | | onlyShowSection="TIME" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="record-log-msg"> |
| | | <div>ç¼éç´è¨ªéç¥ |
| | | <span v-if="item.email && item.phone">(ææ©ç°¡è¨ãEmail)</span> |
| | | <span v-else-if="item.email">(Email)</span> |
| | | <span v-else>(ææ©ç°¡è¨)</span> |
| | | </div> |
| | | <div class="mt-10">é ç´{{item.interviewDate | formatDate}}</div> |
| | | </div> |
| | | </section> |
| | | <div class="time-line"></div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Component, Prop, Vue } from "nuxt-property-decorator"; |
| | | import { NoticeLogs } from "~/shared/models/appointment.model"; |
| | | |
| | | @Component({ |
| | | filters: { |
| | | formatYear(value) { |
| | | if (value) { |
| | | return new Date(value).getFullYear(); |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | export default class RecordCard extends Vue { |
| | | @Prop() |
| | | noticeLogsList!: NoticeLogs[]; |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .record-log-component{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | .record-log-card{ |
| | | display: flex; |
| | | .record-log-card-date-container{ |
| | | position:relative; |
| | | .record-log-card-date-container-circle{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 50%; |
| | | border:1px solid $PRIMARY_BLACK; |
| | | justify-content: center; |
| | | align-items: center; |
| | | align-content: center; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .mt-4{ |
| | | margin-top: 4px; |
| | | } |
| | | .line-space{ |
| | | letter-spacing: 1px; |
| | | } |
| | | .line-height{ |
| | | line-height:1.2; |
| | | } |
| | | .time-line{ |
| | | border-left: 1px solid black; |
| | | height: 30px; |
| | | margin-left: 28px; |
| | | |
| | | } |
| | | .record-log-msg{ |
| | | margin-left: 13px; |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| | |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Context } from '@nuxt/types'; |
| | | |
| | | import { Vue, Component } from 'vue-property-decorator'; |
| | | import { namespace } from 'nuxt-property-decorator'; |
| | | |
| | | import appointmentService from '~/shared/services/appointment.service'; |
| | | import { Appointment } from '~/shared/models/appointment.model'; |
| | | import { ContactStatus } from '~/shared/models/enum/contact-status'; |
| | | |
¤ñ¹ï·sÀÉ®× |
| | |
| | | |
| | | <template> |
| | | <div> |
| | | <div class="text--right mb-30"> |
| | | <div class="pam-link-button--lg" |
| | | @click="addInterview">+æ°å¢</div> |
| | | </div> |
| | | <InterviewCard :interviewList="appointmentDetail.interviewRecordDTOs"></InterviewCard> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Component, namespace, Vue } from "nuxt-property-decorator"; |
| | | import { Appointment } from "~/shared/models/appointment.model"; |
| | | |
| | | const appointmentStore = namespace('appointment.store'); |
| | | |
| | | @Component |
| | | export default class InterviewList extends Vue { |
| | | @appointmentStore.State |
| | | appointmentDetail!: Appointment; |
| | | |
| | | appointmentId!: number; |
| | | |
| | | //////////////////////////////////////////////////////// |
| | | |
| | | mounted() { |
| | | this.appointmentId = +this.$route.params.appointmentId; |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////// |
| | | |
| | | addInterview(): void { |
| | | this.$router.push(`/appointment/${this.appointmentId}/interview/new`); |
| | | } |
| | | |
| | | } |
| | | </script> |
¤ñ¹ï·sÀÉ®× |
| | |
| | | |
| | | |
| | | <template> |
| | | <InterviewRecordCard :noticeLogsList="displayLogs"></InterviewRecordCard> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Component, namespace, Vue, Watch } from "nuxt-property-decorator"; |
| | | import { Appointment, NoticeLogs } from "~/shared/models/appointment.model"; |
| | | |
| | | const appointmentStore = namespace('appointment.store'); |
| | | |
| | | @Component |
| | | export default class RecordList extends Vue { |
| | | @appointmentStore.State |
| | | appointmentDetail!: Appointment; |
| | | |
| | | displayLogs: NoticeLogs[] = []; |
| | | |
| | | //////////////////////////////////////////////////////// |
| | | |
| | | @Watch('appointmentDetail', {immediate: true}) |
| | | onAppointmentDetailChange() { |
| | | if (this.appointmentDetail?.appointmentNoticeLogs.length) { |
| | | this.displayLogs = this.appointmentDetail?.appointmentNoticeLogs |
| | | .map((i) => ({ ...i, sortDate: new Date(i.createdDate)})) |
| | | .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate); |
| | | } |
| | | } |
| | | |
| | | } |
| | | </script> |
| | |
| | | import { Role } from '~/shared/models/enum/Role'; |
| | | import messageBoxService from '~/shared/services/message-box.service'; |
| | | import loginService from '~/shared/services/login.service' |
| | | import { AgentInfo } from '~/shared/models/agent-info.model'; |
| | | |
| | | const loginStore = namespace('login.store'); |
| | | const roleStorage = namespace('localStorage'); |
| | | |
| | | @Component({ |
| | | layout: 'home' |
| | | }) |
| | |
| | | |
| | | @roleStorage.Mutation |
| | | storageConsultantId!:(id:string) => void; |
| | | |
| | | @loginStore.Action |
| | | getLoginConsultantDetail!: (agentNo: string) => Promise<AgentInfo>; |
| | | |
| | | consultantDto = { |
| | | password: '', |
| | |
| | | private verify():void{ |
| | | loginService.getVerificationStatus(this.verificationCode).then( verifySuccess => { |
| | | if(verifySuccess.data){ |
| | | this.loginWithConsultant() |
| | | this.loginWithConsultant(); |
| | | }else{ |
| | | this.clearValue(); |
| | | this.regenerateImgOfVerification(); |
| | |
| | | |
| | | private loginWithConsultant(): void { |
| | | loginService.logInToConsultant(this.consultantDto).then(res => { |
| | | this.getLoginConsultantDetail(this.consultantDto.username); |
| | | this.storageIdToken(res.data.id_token); |
| | | this.storageRole(Role.ADMIN); |
| | | this.storageConsultantId(this.consultantDto.username) |
| | |
| | | export interface AgentInfo { |
| | | name : string; |
| | | agentNo : string; |
| | | role : string; |
| | | img : string; |
| | | avgScore : number; |
| | | title : string; |
| | | phoneNumber : string; |
| | | serveArea : string; |
| | | companyAddress : string; |
| | | latestLoginTime : Date ; |
| | | seniority : string; |
| | | suitability : number; |
| | | evaluation : number; |
| | | expertise : string[]; |
| | | concept : string; |
| | | experiences : string; |
| | | awards : string; |
| | | gender : string, |
| | | communicationStyle: string; |
| | | companyAddress : string; |
| | | concept : string; |
| | | email? : string; |
| | | evaluation : number; |
| | | experiences : string; |
| | | expertise : string[]; |
| | | gender : string, |
| | | img : string; |
| | | latestLoginTime : Date ; |
| | | name : string; |
| | | phoneNumber : string; |
| | | role : string; |
| | | seniority : string; |
| | | serveArea : string; |
| | | suitability : number; |
| | | title : string; |
| | | } |
| | |
| | | .filter(item => item.communicateStatus === this.contactStatus.DONE || item.communicateStatus === this.contactStatus.CLOSE ).length; |
| | | } |
| | | |
| | | get isCloseAppointment(): boolean { |
| | | const closedStatusList = [this.contactStatus.DONE, this.contactStatus.CLOSE, this.contactStatus.CANCEL]; |
| | | return closedStatusList.includes(this.appointmentDetail!.communicateStatus); |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | @Mutation |
| | |
| | | } |
| | | |
| | | @Action({ commit: 'SET_APPOINTMENT'}) |
| | | async setAppointmentDetail(appointmentId: number): Promise<Appointment> { |
| | | async getAppointmentDetail(appointmentId: number): Promise<Appointment> { |
| | | if (this.appointmentDetail && this.appointmentDetail.id === appointmentId) { |
| | | return this.appointmentDetail; |
| | | } else { |
| | |
| | | import { StrictQueryParams } from '~/shared/models/strict-query.model'; |
| | | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' |
| | | |
| | | import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant'; |
| | | |
| | | import myConsultantService from '~/shared/services/my-consultant.service'; |
| | | import queryConsultantService from '~/shared/services/query-consultant.service'; |
| | | import appointmentService from '~/shared/services/appointment.service'; |
| | | import reviewsService from '~/shared/services/reviews.service'; |
| | | |
| | | import { Consultant } from '~/shared/models/consultant.model'; |
| | | import { Appointment, AppointmentLog, InterviewRecord } from '~/shared/models/appointment.model'; |
| | | import { AgentOfStrictQuery } from '~/shared/models/strict-query.model'; |
| | | import { AgentInfo } from '~/shared/models/agent-info.model'; |
| | | import { agentCommunicationStyleList } from '~/shared/const/agent-communication-style-list'; |
| | | import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant'; |
| | | import { AppointmentLog } from '~/shared/models/appointment.model'; |
| | | import { AgentOfStrictQuery, StrictQueryParams } from '~/shared/models/strict-query.model'; |
| | | |
| | | @Module |
| | | export default class Store extends VuexModule { |
| | | recommendList: Consultant[] = []; |
| | |
| | | myConsultantList: Consultant[] = []; |
| | | |
| | | myAppointmentReviewLogList: AppointmentLog[] = []; |
| | | interviewRecord: InterviewRecord = { |
| | | appointmentId : 0, |
| | | content : '', |
| | | createdBy : '', |
| | | createdDate : '', |
| | | id : 0, |
| | | interviewDate : '', |
| | | lastModifiedBy : '', |
| | | lastModifiedDate: '' |
| | | } |
| | | |
| | | get isUserLogin() { |
| | | return this.context.getters['localStorage/isUserLogin']; |
| | |
| | | @Mutation |
| | | updateMyAppointmentReviewLog(data: AppointmentLog[]) { |
| | | this.myAppointmentReviewLogList = data; |
| | | } |
| | | |
| | | @Mutation |
| | | updateInterviewRecord(data: InterviewRecord) { |
| | | this.interviewRecord = data; |
| | | } |
| | | |
| | | @Mutation |
| | | clearInterviewRecord() { |
| | | this.interviewRecord = { |
| | | appointmentId : 0, |
| | | content : '', |
| | | createdBy : '', |
| | | createdDate : '', |
| | | id : 0, |
| | | interviewDate : '', |
| | | lastModifiedBy : '', |
| | | lastModifiedDate: '' |
| | | } |
| | | } |
| | | |
| | | @Action |
| | |
| | | localStorage.removeItem('current_role'); |
| | | localStorage.removeItem('consultant_id'); |
| | | localStorage.removeItem('appointment'); |
| | | localStorage.removeItem('login_consultant'); |
| | | this.id_token = localStorage.getItem('id_token'); |
| | | this.current_role = localStorage.getItem('current_role'); |
| | | this.consultant_id = localStorage.getItem('consultant_id'); |
¤ñ¹ï·sÀÉ®× |
| | |
| | | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' |
| | | |
| | | import myConsultantService from '~/shared/services/my-consultant.service'; |
| | | import { AgentInfo } from '~/shared/models/agent-info.model'; |
| | | |
| | | @Module |
| | | export default class AppointmentStore extends VuexModule { |
| | | |
| | | loginConsultant?: AgentInfo = JSON.parse(localStorage.getItem('login_consultant')!); |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | @Mutation |
| | | SET_LOGIN_CONSULTANT(agentInfo: AgentInfo): void { |
| | | this.loginConsultant = agentInfo; |
| | | localStorage.setItem('login_consultant', JSON.stringify(agentInfo)); |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | |
| | | @Action({ commit: 'SET_LOGIN_CONSULTANT' }) |
| | | async getLoginConsultantDetail(agentNo: string): Promise<AgentInfo> { |
| | | return await myConsultantService.getConsultantDetail(agentNo).then((res) => res); |
| | | } |
| | | |
| | | } |