Add support for Notes tab in profile view

This commit is contained in:
Lance Edgar 2023-06-17 14:18:43 -05:00
parent 105dab7a3d
commit d77de76c97
2 changed files with 357 additions and 32 deletions

View file

@ -1090,6 +1090,153 @@
</b-tab-item> </b-tab-item>
</%def> </%def>
<%def name="render_notes_tab_template()">
<script type="text/x-template" id="notes-tab-template">
<div>
% if request.has_perm('people_profile.add_note'):
<b-button type="is-primary"
class="control"
@click="noteNew()"
icon-pack="fas"
icon-left="plus">
Add Note
</b-button>
% endif
<b-table :data="notes">
<b-table-column field="note_type"
label="Type"
v-slot="props">
{{ props.row.note_type_display }}
</b-table-column>
<b-table-column field="subject"
label="Subject"
v-slot="props">
{{ props.row.subject }}
</b-table-column>
<b-table-column field="text"
label="Text"
v-slot="props">
{{ props.row.text }}
</b-table-column>
<b-table-column field="created"
label="Created"
v-slot="props">
<span v-html="props.row.created_display"></span>
</b-table-column>
<b-table-column field="created_by"
label="Created By"
v-slot="props">
{{ props.row.created_by_display }}
</b-table-column>
% if request.has_any_perm('people_profile.edit_note', 'people_profile.delete_note'):
<b-table-column label="Actions"
v-slot="props">
% if request.has_perm('people_profile.edit_note'):
<a href="#" @click.prevent="noteEdit(props.row)">
<i class="fas fa-edit"></i>
Edit
</a>
% endif
% if request.has_perm('people_profile.delete_note'):
<a href="#" @click.prevent="noteDelete(props.row)"
class="has-text-danger">
<i class="fas fa-trash"></i>
Delete
</a>
% endif
</b-table-column>
% endif
</b-table>
<b-modal :active.sync="noteShowDialog"
has-modal-card>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
{{ noteDialogTitle }}
</p>
</header>
<section class="modal-card-body">
<b-field label="Type"
:type="!noteDeleting && !noteType ? 'is-danger' : null">
<b-select v-model="noteType"
:disabled="noteUUID">
<option v-for="option in noteTypeOptions"
:key="option.value"
:value="option.value">
{{ option.label }}
</option>
</b-select>
</b-field>
<b-field label="Subject">
<b-input v-model.trim="noteSubject"
:disabled="noteDeleting">
</b-input>
</b-field>
<b-field label="Text">
<b-input v-model.trim="noteText"
type="textarea"
:disabled="noteDeleting">
</b-input>
</b-field>
<b-notification v-if="noteDeleting"
type="is-danger"
:closable="false">
Are you sure you wish to delete this note?
</b-notification>
</section>
<footer class="modal-card-foot">
<b-button :type="noteDeleting ? 'is-danger' : 'is-primary'"
@click="noteSave()"
:disabled="noteSaving || (!noteDeleting && !noteType)"
icon-pack="fas"
icon-left="save">
{{ noteSaving ? "Working, please wait..." : noteSaveText }}
</b-button>
<b-button @click="noteShowDialog = false">
Cancel
</b-button>
</footer>
</div>
</b-modal>
</div>
</script>
</%def>
<%def name="render_notes_tab()">
<b-tab-item label="Notes"
value="notes"
icon-pack="fas"
:icon="notes.length ? 'check' : null">
<notes-tab :notes="notes"
:note-type-options="noteTypeOptions"
@new-notes-data="newNotesData">
</notes-tab>
</b-tab-item>
</%def>
<%def name="render_user_tab()"> <%def name="render_user_tab()">
<b-tab-item label="User" <b-tab-item label="User"
value="user" value="user"
@ -1154,6 +1301,7 @@
${self.render_shopper_tab()} ${self.render_shopper_tab()}
% endif % endif
${self.render_employee_tab()} ${self.render_employee_tab()}
${self.render_notes_tab()}
${self.render_user_tab()} ${self.render_user_tab()}
</%def> </%def>
@ -1271,6 +1419,7 @@
${parent.render_this_page_template()} ${parent.render_this_page_template()}
${self.render_personal_tab_template()} ${self.render_personal_tab_template()}
${self.render_employee_tab_template()} ${self.render_employee_tab_template()}
${self.render_notes_tab_template()}
${self.render_profile_info_template()} ${self.render_profile_info_template()}
</%def> </%def>
@ -1833,6 +1982,136 @@
</script> </script>
</%def> </%def>
<%def name="declare_notes_tab_vars()">
<script type="text/javascript">
let NotesTabData = {
noteShowDialog: false,
noteUUID: null,
noteType: null,
noteSubject: null,
noteText: null,
noteDeleting: false,
noteSaving: false,
}
let NotesTab = {
template: '#notes-tab-template',
mixins: [SimpleRequestMixin],
props: {
notes: Array,
noteTypeOptions: Array,
},
computed: {
noteDialogTitle() {
if (this.noteUUID) {
if (this.noteDeleting) {
return "Delete Note"
}
return "Edit Note"
}
return "New Note"
},
noteSaveText() {
if (this.noteDeleting) {
return "Delete Note"
}
return "Save Note"
},
},
methods: {
% if request.has_perm('people_profile.add_note'):
noteNew() {
this.noteUUID = null
this.noteType = null
this.noteSubject = null
this.noteText = null
this.noteDeleting = false
this.noteShowDialog = true
},
% endif
% if request.has_perm('people_profile.edit_note'):
noteEdit(note) {
this.noteUUID = note.uuid
this.noteType = note.note_type
this.noteSubject = note.subject
this.noteText = note.text
this.noteDeleting = false
this.noteShowDialog = true
},
% endif
% if request.has_perm('people_profile.delete_note'):
noteDelete(note) {
this.noteUUID = note.uuid
this.noteType = note.note_type
this.noteSubject = note.subject
this.noteText = note.text
this.noteDeleting = true
this.noteShowDialog = true
},
% endif
% if request.has_any_perm('people_profile.add_note', 'people_profile.edit_note', 'people_profile.delete_note'):
noteSave() {
this.noteSaving = true
let url = null
if (!this.noteUUID) {
url = '${master.get_action_url('profile_add_note', instance)}'
} else if (this.noteDeleting) {
url = '${master.get_action_url('profile_delete_note', instance)}'
} else {
url = '${master.get_action_url('profile_edit_note', instance)}'
}
let params = {
uuid: this.noteUUID,
note_type: this.noteType,
note_subject: this.noteSubject,
note_text: this.noteText,
}
this.simplePOST(url, params, response => {
this.$emit('new-notes-data', response.data.notes)
this.noteSaving = false
this.noteShowDialog = false
}, response => {
this.notesSaving = false
})
},
% endif
},
}
</script>
</%def>
<%def name="make_notes_tab_component()">
${self.declare_notes_tab_vars()}
<script type="text/javascript">
NotesTab.data = function() { return NotesTabData }
Vue.component('notes-tab', NotesTab)
</script>
</%def>
<%def name="declare_profile_info_vars()"> <%def name="declare_profile_info_vars()">
<script type="text/javascript"> <script type="text/javascript">
@ -1847,6 +2126,8 @@
members: ${json.dumps(members_data)|n}, members: ${json.dumps(members_data)|n},
employee: ${json.dumps(employee_data)|n}, employee: ${json.dumps(employee_data)|n},
employeeHistory: ${json.dumps(employee_history_data)|n}, employeeHistory: ${json.dumps(employee_history_data)|n},
notes: ${json.dumps(notes_data)|n},
noteTypeOptions: ${json.dumps(note_type_options)|n},
users: ${json.dumps(users_data)|n}, users: ${json.dumps(users_data)|n},
phoneTypeOptions: ${json.dumps(phone_type_options)|n}, phoneTypeOptions: ${json.dumps(phone_type_options)|n},
emailTypeOptions: ${json.dumps(email_type_options)|n}, emailTypeOptions: ${json.dumps(email_type_options)|n},
@ -1876,6 +2157,10 @@
computed: {}, computed: {},
methods: { methods: {
newNotesData(notes) {
this.notes = notes
},
personUpdated(person) { personUpdated(person) {
this.person = person this.person = person
}, },
@ -2001,6 +2286,7 @@
${parent.make_this_page_component()} ${parent.make_this_page_component()}
${self.make_personal_tab_component()} ${self.make_personal_tab_component()}
${self.make_employee_tab_component()} ${self.make_employee_tab_component()}
${self.make_notes_tab_component()}
${self.make_profile_info_component()} ${self.make_profile_info_component()}
</%def> </%def>

View file

@ -60,7 +60,6 @@ class PersonView(MasterView):
has_versions = True has_versions = True
bulk_deletable = True bulk_deletable = True
is_contact = True is_contact = True
manage_notes_from_profile_view = False
supports_autocomplete = True supports_autocomplete = True
configurable = True configurable = True
@ -460,6 +459,8 @@ class PersonView(MasterView):
'employee_view_url': self.request.route_url('employees.view', uuid=employee.uuid) if employee else None, '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': employee.get_current_history() if employee else None,
'employee_history_data': self.get_context_employee_history(employee), 'employee_history_data': self.get_context_employee_history(employee),
'notes_data': self.get_context_notes(person),
'note_type_options': self.get_note_type_options(),
'users_data': self.get_context_users(person), 'users_data': self.get_context_users(person),
'dynamic_content_title': self.get_context_content_title(person), 'dynamic_content_title': self.get_context_content_title(person),
} }
@ -752,6 +753,29 @@ class PersonView(MasterView):
}) })
return data return data
def get_context_notes(self, person):
data = []
notes = sorted(person.notes, key=lambda n: n.created, reverse=True)
for note in notes:
data.append(self.get_context_note(note))
return data
def get_context_note(self, note):
app = self.get_rattail_app()
return {
'uuid': note.uuid,
'note_type': note.type,
'note_type_display': self.enum.PERSON_NOTE_TYPE.get(note.type, note.type),
'subject': note.subject,
'text': note.text,
'created_display': raw_datetime(self.rattail_config, note.created),
'created_by_display': str(note.created_by),
}
def get_note_type_options(self):
return [{'value': k, 'label': v}
for k, v in self.enum.PERSON_NOTE_TYPE.items()]
def get_context_users(self, person): def get_context_users(self, person):
data = [] data = []
users = person.users users = person.users
@ -1385,6 +1409,8 @@ class PersonView(MasterView):
if mode == 'create': if mode == 'create':
del schema['uuid'] del schema['uuid']
form = forms.Form(schema=schema, request=self.request) form = forms.Form(schema=schema, request=self.request)
if mode != 'delete':
form.set_validator('note_type', colander.OneOf(self.enum.PERSON_NOTE_TYPE))
return form return form
def profile_add_note(self): def profile_add_note(self):
@ -1406,11 +1432,15 @@ class PersonView(MasterView):
person.notes.append(note) person.notes.append(note)
return note return note
def profile_add_note_success(self, note): def profile_add_note_success(self, note, person=None):
return self.redirect(self.get_action_url('view_profile', person)) return {
'notes': self.get_context_notes(person or note.person),
}
def profile_add_note_failure(self, person, form): def profile_add_note_failure(self, person, form):
return self.redirect(self.get_action_url('view_profile', person)) return {
'error': str(form.make_deform_form().error),
}
def profile_edit_note(self): def profile_edit_note(self):
person = self.get_instance() person = self.get_instance()
@ -1429,10 +1459,10 @@ class PersonView(MasterView):
return note return note
def profile_edit_note_success(self, note): def profile_edit_note_success(self, note):
return self.redirect(self.get_action_url('view_profile', person)) return self.profile_add_note_success(note)
def profile_edit_note_failure(self, person, form): def profile_edit_note_failure(self, person, form):
return self.redirect(self.get_action_url('view_profile', person)) return self.profile_add_note_failure(person, form)
def profile_delete_note(self): def profile_delete_note(self):
person = self.get_instance() person = self.get_instance()
@ -1449,10 +1479,10 @@ class PersonView(MasterView):
self.Session.delete(note) self.Session.delete(note)
def profile_delete_note_success(self, person): def profile_delete_note_success(self, person):
return self.redirect(self.get_action_url('view_profile', person)) return self.profile_add_note_success(None, person=person)
def profile_delete_note_failure(self, person, form): def profile_delete_note_failure(self, person, form):
return self.redirect(self.get_action_url('view_profile', person)) return self.profile_add_note_failure(person, form)
def make_user(self): def make_user(self):
uuid = self.request.POST['person_uuid'] uuid = self.request.POST['person_uuid']
@ -1657,32 +1687,41 @@ class PersonView(MasterView):
permission='people_profile.view_versions', permission='people_profile.view_versions',
renderer='json') renderer='json')
# manage notes from profile view # profile - add note
if cls.manage_notes_from_profile_view: config.add_tailbone_permission('people_profile',
'people_profile.add_note',
"Add new Note records")
config.add_route(f'{route_prefix}.profile_add_note',
f'{instance_url_prefix}/profile/new-note',
request_method='POST')
config.add_view(cls, attr='profile_add_note',
route_name=f'{route_prefix}.profile_add_note',
permission='people_profile.add_note',
renderer='json')
# add note # profile - edit note
config.add_tailbone_permission('people_profile', 'people_profile.add_note', config.add_tailbone_permission('people_profile',
"Add new {} Note records".format(model_title)) 'people_profile.edit_note',
config.add_route('{}.profile_add_note'.format(route_prefix), '{}/{{{}}}/profile/new-note'.format(url_prefix, model_key), "Edit Note records")
request_method='POST') config.add_route(f'{route_prefix}.profile_edit_note',
config.add_view(cls, attr='profile_add_note', route_name='{}.profile_add_note'.format(route_prefix), f'{instance_url_prefix}/profile/edit-note',
permission='people_profile.add_note') request_method='POST')
config.add_view(cls, attr='profile_edit_note',
route_name=f'{route_prefix}.profile_edit_note',
permission='people_profile.edit_note',
renderer='json')
# edit note # profile - delete note
config.add_tailbone_permission('people_profile', 'people_profile.edit_note', config.add_tailbone_permission('people_profile',
"Edit {} Note records".format(model_title)) 'people_profile.delete_note',
config.add_route('{}.profile_edit_note'.format(route_prefix), '{}/{{{}}}/profile/edit-note'.format(url_prefix, model_key), "Delete Note records")
request_method='POST') config.add_route(f'{route_prefix}.profile_delete_note',
config.add_view(cls, attr='profile_edit_note', route_name='{}.profile_edit_note'.format(route_prefix), f'{instance_url_prefix}/profile/delete-note',
permission='people_profile.edit_note') request_method='POST')
config.add_view(cls, attr='profile_delete_note',
# delete note route_name=f'{route_prefix}.profile_delete_note',
config.add_tailbone_permission('people_profile', 'people_profile.delete_note', permission='people_profile.delete_note',
"Delete {} Note records".format(model_title)) renderer='json')
config.add_route('{}.profile_delete_note'.format(route_prefix), '{}/{{{}}}/profile/delete-note'.format(url_prefix, model_key),
request_method='POST')
config.add_view(cls, attr='profile_delete_note', route_name='{}.profile_delete_note'.format(route_prefix),
permission='people_profile.delete_note')
# make user for person # make user for person
config.add_route('{}.make_user'.format(route_prefix), '{}/make-user'.format(url_prefix), config.add_route('{}.make_user'.format(route_prefix), '{}/make-user'.format(url_prefix),