Refactor the Employee tab of profile view, per better patterns

learned some things from the Personal tab overhaul
This commit is contained in:
Lance Edgar 2021-10-05 08:25:33 -04:00
parent 6386b34516
commit e7fb1559f5
2 changed files with 161 additions and 225 deletions

View file

@ -452,20 +452,19 @@
</div>
</%def>
<%def name="render_personal_tab_cards()">
${self.render_personal_name_card()}
${self.render_personal_address_card()}
${self.render_personal_phone_card()}
${self.render_personal_email_card()}
</%def>
<%def name="render_personal_tab_template()">
<script type="text/x-template" id="personal-tab-template">
<div style="display: flex; justify-content: space-between;">
<div style="flex-grow: 1; margin-right: 1rem;">
${self.render_personal_name_card()}
${self.render_personal_address_card()}
${self.render_personal_phone_card()}
${self.render_personal_email_card()}
${self.render_personal_tab_cards()}
</div>
<div>
@ -670,7 +669,7 @@
<div style="flex-grow: 1;">
<div v-if="employee.exists">
<div v-if="employee.uuid">
<b-field horizontal label="Employee ID">
<div class="level">
@ -721,22 +720,22 @@
</b-field>
<b-field horizontal label="Employee Status">
<span>{{ employee.current ? "current" : "former" }}</span>
<span>{{ employee.status_display }}</span>
</b-field>
<b-field horizontal label="Start Date">
<span>{{ employee.startDate }}</span>
<span>{{ employee.start_date }}</span>
</b-field>
<b-field horizontal label="End Date">
<span>{{ employee.endDate }}</span>
<span>{{ employee.end_date }}</span>
</b-field>
<br />
<p><strong>Employee History</strong></p>
<br />
<b-table :data="employeeHistory.data">
<b-table :data="employeeHistory">
<template slot-scope="props">
<b-table-column field="start_date" label="Start Date">
@ -761,7 +760,7 @@
</div>
<p v-if="!employee.exists">
<p v-if="!employee.uuid">
${person} has never been an employee.
</p>
@ -774,7 +773,7 @@
<b-button v-if="!employee.current"
type="is-primary"
@click="showStartEmployee()">
@click="startEmployeeInit()">
${person} is now an Employee
</b-button>
@ -785,7 +784,7 @@
</b-button>
<b-modal has-modal-card
:active.sync="showStartEmployeeDialog">
:active.sync="startEmployeeShowDialog">
<div class="modal-card">
<header class="modal-card-head">
@ -802,7 +801,7 @@
</section>
<footer class="modal-card-foot">
<b-button @click="showStartEmployeeDialog = false">
<b-button @click="startEmployeeShowDialog = false">
Cancel
</b-button>
<once-button type="is-primary"
@ -882,8 +881,8 @@
% endif
% if request.has_perm('employees.view'):
<b-button v-if="employee.viewURL"
tag="a" :href="employee.viewURL">
<b-button v-if="employee.view_url"
tag="a" :href="employee.view_url">
View Employee
</b-button>
% endif
@ -902,81 +901,85 @@
:icon="employee.current ? 'check' : null">
<employee-tab :employee="employee"
:employee-history="employeeHistory"
@employee-updated="employeeUpdated"
@employee-history-updated="employeeHistoryUpdated"
@change-content-title="changeContentTitle">
</employee-tab>
</b-tab-item>
</%def>
<%def name="render_user_tab()">
<b-tab-item label="User" ${'icon="check" icon-pack="fas"' if person.users else ''|n}>
% if person.users:
<p>${person} is associated with <strong>${len(person.users)}</strong> user account(s)</p>
<br />
<div id="users-accordion">
% for user in person.users:
<b-collapse class="panel"
## TODO: what's up with aria-id here?
## aria-id="contentIdForA11y2"
>
<div
slot="trigger"
class="panel-heading"
role="button"
## TODO: what's up with aria-id here?
## aria-controls="contentIdForA11y2"
>
<strong>${user.username}</strong>
</div>
<div class="panel-block">
<div style="display: flex; justify-content: space-between; width: 100%;">
<div>
<div class="field-wrapper id">
<div class="field-row">
<label>Username</label>
<div class="field">
${user.username}
</div>
</div>
</div>
</div>
<div>
% if request.has_perm('users.view'):
${h.link_to("View User", url('users.view', uuid=user.uuid), class_='button')}
% endif
</div>
</div>
</div>
</b-collapse>
% endfor
</div>
% else:
<p>${person} has never been a user.</p>
% endif
</b-tab-item><!-- User -->
</%def>
<%def name="render_profile_tabs()">
${self.render_personal_tab()}
${self.render_customer_tab()}
${self.render_member_tab()}
${self.render_employee_tab()}
${self.render_user_tab()}
</%def>
<%def name="render_profile_info_template()">
<script type="text/x-template" id="profile-info-template">
<div>
<b-tabs v-model="activeTab" type="is-boxed">
${self.render_personal_tab()}
${self.render_customer_tab()}
${self.render_member_tab()}
${self.render_employee_tab()}
<b-tab-item label="User" ${'icon="check" icon-pack="fas"' if person.users else ''|n}>
% if person.users:
<p>${person} is associated with <strong>${len(person.users)}</strong> user account(s)</p>
<br />
<div id="users-accordion">
% for user in person.users:
<b-collapse class="panel"
## TODO: what's up with aria-id here?
## aria-id="contentIdForA11y2"
>
<div
slot="trigger"
class="panel-heading"
role="button"
## TODO: what's up with aria-id here?
## aria-controls="contentIdForA11y2"
>
<strong>${user.username}</strong>
</div>
<div class="panel-block">
<div style="display: flex; justify-content: space-between; width: 100%;">
<div>
<div class="field-wrapper id">
<div class="field-row">
<label>Username</label>
<div class="field">
${user.username}
</div>
</div>
</div>
</div>
<div>
% if request.has_perm('users.view'):
${h.link_to("View User", url('users.view', uuid=user.uuid), class_='button')}
% endif
</div>
</div>
</div>
</b-collapse>
% endfor
</div>
% else:
<p>${person} has never been a user.</p>
% endif
</b-tab-item><!-- User -->
${self.render_profile_tabs()}
</b-tabs>
</div>
</script>
@ -1362,58 +1365,37 @@
</script>
</%def>
<%def name="set_employee_data()">
<script type="text/javascript">
let EmployeeData = {
exists: ${json.dumps(bool(employee))|n},
viewURL: ${json.dumps(employee_view_url)|n},
current: ${json.dumps(bool(employee and employee.status == enum.EMPLOYEE_STATUS_CURRENT))|n},
startDate: ${json.dumps(six.text_type(employee_history.start_date) if employee_history else None)|n},
endDate: ${json.dumps(six.text_type(employee_history.end_date) if employee_history and employee_history.end_date else None)|n},
id: ${json.dumps(employee.id if employee else None)|n},
}
let EmployeeHistoryData = {
data: ${json.dumps(employee_history_data)|n},
}
</script>
</%def>
<%def name="declare_employee_tab_vars()">
<script type="text/javascript">
let EmployeeTabData = {
startEmployeeShowDialog: false,
employeeID: null,
employeeStartDate: null,
showStopEmployeeDialog: false,
employeeEndDate: null,
employeeRevokeAccess: false,
showEditEmployeeHistoryDialog: false,
employeeHistoryUUID: null,
employeeHistoryStartDate: null,
employeeHistoryEndDate: null,
employeeHistoryEndDateRequired: false,
% if request.has_perm('employees.edit'):
showEditEmployeeIDDialog: false,
newEmployeeID: null,
updatingEmployeeID: false,
% endif
}
let EmployeeTab = {
template: '#employee-tab-template',
mixins: [SubmitMixin],
props: {
employee: Object,
employeeHistory: Object,
},
data() {
return {
showStartEmployeeDialog: false,
employeeID: null,
employeeStartDate: null,
showStopEmployeeDialog: false,
employeeEndDate: null,
employeeRevokeAccess: false,
showEditEmployeeHistoryDialog: false,
employeeHistoryUUID: null,
employeeHistoryStartDate: null,
employeeHistoryEndDate: null,
employeeHistoryEndDateRequired: false,
% if request.has_perm('employees.edit'):
showEditEmployeeIDDialog: false,
newEmployeeID: null,
updatingEmployeeID: false,
% endif
## TODO: should find a better way to handle CSRF token
csrftoken: ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n},
}
},
computed: {
@ -1452,26 +1434,11 @@
'employee_id': this.newEmployeeID,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.success) {
this.employee.id = data.employee.id
this.showEditEmployeeIDDialog = false
this.updatingEmployeeID = false
} else {
this.$buefy.toast.open({
message: "Save failed: " + data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
}
}, response => {
alert("Unexpected error occurred!")
let that = this
this.submitData(url, params, function(response) {
that.$emit('employee-updated', response.data.employee)
that.showEditEmployeeIDDialog = false
that.updatingEmployeeID = false
})
},
@ -1479,13 +1446,12 @@
% if request.has_perm('people_profile.toggle_employee'):
showStartEmployee() {
this.employeeID = this.employee.id
this.showStartEmployeeDialog = true
startEmployeeInit() {
this.employeeID = this.employee.id || null
this.startEmployeeShowDialog = true
},
startEmployee() {
let url = '${url('people.profile_start_employee', uuid=person.uuid)}'
let params = {
@ -1493,44 +1459,26 @@
start_date: this.employeeStartDate,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.success) {
this.startEmployeeSuccess(data)
} else {
this.$buefy.toast.open({
message: "Save failed: " + data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
}
}, response => {
alert("Unexpected error occurred!")
let that = this
this.submitData(url, params, function(response) {
that.startEmployeeSuccess(response.data)
})
},
startEmployeeSuccess(data) {
this.employee.exists = true
this.employee.id = data.employee_id
this.employee.viewURL = data.employee_view_url
this.employee.current = true
this.employee.startDate = data.start_date
this.employee.endDate = null
this.employeeHistory.data = data.employee_history_data
// this.customerNumber = data.customer_number
this.employeeEndDate = null
this.$emit('employee-updated', data.employee)
this.$emit('employee-history-updated', data.employee_history_data)
this.$emit('change-content-title', data.dynamic_content_title)
// this.posTabStale = true
this.showStartEmployeeDialog = false
// let derived component do more here if needed
this.startEmployeeSuccessExtra(data)
this.startEmployeeShowDialog = false
},
endEmployee() {
startEmployeeSuccessExtra(data) {},
endEmployee() {
let url = '${url('people.profile_end_employee', uuid=person.uuid)}'
let params = {
@ -1538,38 +1486,25 @@
revoke_access: this.employeeRevokeAccess,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.success) {
this.endEmployeeSuccess(data)
} else {
this.$buefy.toast.open({
message: "Save failed: " + data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
}
}, response => {
alert("Unexpected error occurred!")
let that = this
this.submitData(url, params, function(response) {
that.endEmployeeSuccess(response.data)
})
},
endEmployeeSuccess(data) {
this.employee.current = false
this.employee.endDate = data.end_date
this.employeeHistory.data = data.employee_history_data
this.employeeStartDate = null
this.$emit('employee-updated', data.employee)
this.$emit('employee-history-updated', data.employee_history_data)
this.$emit('change-content-title', data.dynamic_content_title)
// this.memberTabStale = true
// this.posTabStale = true
// let derived component do more here if needed
this.startEmployeeSuccessExtra(data)
this.showStopEmployeeDialog = false
},
endEmployeeSuccessExtra(data) {},
% endif
% if request.has_perm('people_profile.edit_employee_history'):
@ -1583,7 +1518,6 @@
},
saveEmployeeHistory() {
let url = '${url('people.profile_edit_employee_history', uuid=person.uuid)}'
let params = {
@ -1592,19 +1526,11 @@
end_date: this.employeeHistoryEndDate,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.success) {
this.employee.startDate = data.start_date
this.employee.endDate = data.end_date
this.employeeHistory.data = data.employee_history_data
}
this.showEditEmployeeHistoryDialog = false
let that = this
this.submitData(url, params, function(response) {
that.$emit('employee-updated', response.data.employee)
that.$emit('employee-history-updated', response.data.employee_history_data)
that.showEditEmployeeHistoryDialog = false
})
},
@ -1619,6 +1545,7 @@
${self.declare_employee_tab_vars()}
<script type="text/javascript">
EmployeeTab.data = function() { return EmployeeTabData }
Vue.component('employee-tab', EmployeeTab)
</script>
@ -1633,8 +1560,8 @@
customers: ${json.dumps(customers_data)|n},
member: null, // TODO
members: ${json.dumps(members_data)|n},
employee: EmployeeData,
employeeHistory: EmployeeHistoryData,
employee: ${json.dumps(employee_data)|n},
employeeHistory: ${json.dumps(employee_history_data)|n},
phoneTypeOptions: ${json.dumps(phone_type_options)|n},
emailTypeOptions: ${json.dumps(email_type_options)|n},
maxLengths: ${json.dumps(max_lengths)|n},
@ -1647,6 +1574,12 @@
personUpdated(person) {
this.person = person
},
employeeUpdated(employee) {
this.employee = employee
},
employeeHistoryUpdated(employeeHistory) {
this.employeeHistory = employeeHistory
},
changeContentTitle(newTitle) {
this.$emit('change-content-title', newTitle)
},
@ -1722,7 +1655,6 @@
<%def name="make_this_page_component()">
${parent.make_this_page_component()}
${self.make_personal_tab_component()}
${self.set_employee_data()}
${self.make_employee_tab_component()}
${self.make_profile_info_component()}
</%def>

View file

@ -441,6 +441,7 @@ class PersonView(MasterView):
'customers_data': self.get_context_customers(person),
'members_data': self.get_context_members(person),
'employee': employee,
'employee_data': self.get_context_employee(employee) if employee else {},
'employee_view_url': self.request.route_url('employees.view', uuid=employee.uuid) if employee else None,
'employee_history': employee.get_current_history() if employee else None,
'employee_history_data': self.get_context_employee_history(employee),
@ -581,6 +582,7 @@ class PersonView(MasterView):
app = self.get_rattail_app()
handler = app.get_employment_handler()
context = handler.get_context_employee(employee)
context['view_url'] = self.request.route_url('employees.view', uuid=employee.uuid)
return context
def get_context_employee_history(self, employee):
@ -884,6 +886,7 @@ class PersonView(MasterView):
start_date = datetime.datetime.strptime(data['start_date'], '%Y-%m-%d').date()
employee = handler.begin_employment(person, start_date,
employee_id=data['id'])
self.Session.flush()
return self.profile_start_employee_result(employee, start_date)
def profile_start_employee_result(self, employee, start_date):
@ -912,6 +915,7 @@ class PersonView(MasterView):
employee = handler.get_employee(person)
handler.end_employment(employee, end_date,
revoke_access=data.get('revoke_access'))
self.Session.flush()
return self.profile_end_employee_result(employee, end_date)
def profile_end_employee_result(self, employee, end_date):
@ -948,9 +952,9 @@ class PersonView(MasterView):
self.Session.flush()
current_history = employee.get_current_history()
return {
'success': True,
'employee': self.get_context_employee(employee),
'start_date': six.text_type(current_history.start_date),
'end_date': six.text_type(current_history.end_date or ''),
'employee_history_data': self.get_context_employee_history(employee),