Skip to content

Commit

Permalink
feat(network): add emailing of Locals about their Antenna Criteria fu…
Browse files Browse the repository at this point in the history
…lfilment (#2206)

* feat(network): Add assignment of NetCommie to Local

* chore(network): Update permission checking

* feat(network): Add editing of AC mail template text

* chore(network): Change intend of mail modal

* chore(network): Remove the button to send emails to all Locals

* feat(network): Add modal to send AC fulfilment emails with template text

Also adds the missing 'Agora attendance' that I missed when making the previous modal

* chore(network): Correctly capitalise Local type

* feat(network): Add assigned NetCom in copy of mail

* chore(network): Restructure logic of needed Antenna Criteria

* fix(network): Fix correctly showing of tags for fulfilment

Fixes that some criteria did not display exceptions correctly

TODO: update the display of the detailed information to have the same structure

* chore(network): Fix lint

* fix(network): correctly use mail template

* chore(network): update AC to be in line with CIA 35.1

* fix(network): correct find NetCom body in database

* chore(network): fix lint

* fix(network): correctly rerender updating NetCom assignment

Also removes previous merging error with permissions
  • Loading branch information
LeonVreling authored Dec 6, 2024
1 parent 1678d39 commit 6671d8d
Show file tree
Hide file tree
Showing 4 changed files with 404 additions and 15 deletions.
94 changes: 93 additions & 1 deletion src/views/network/AntennaCriteriaCheck.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

<a class="button is-info" v-if="hideSafeLocals" @click="toggleHideSafeLocals()">Show all Locals</a>
<a class="button is-info" v-if="!hideSafeLocals" @click="toggleHideSafeLocals()">Show only Locals in danger</a>

<a class="button is-info" v-if="can.sendFulfilmentEmails" @click="openAntennaCriteriaMail()">Change fulfilment email text</a>
</div>
<b-table :data="filteredBodies" :loading="isLoading" narrowed>
<template slot-scope="props">
Expand All @@ -30,6 +32,10 @@
{{ props.row.type | capitalize }}
</b-table-column>

<b-table-column sortable field="netcom" label="NetCom">
{{ props.row.netcom?.first_name }}
</b-table-column>

<b-table-column sortable field="status" label="Status">
<b-tag :type="statusType(props.row)" size="is-medium">{{ statusValue(props.row) }}</b-tag>
</b-table-column>
Expand Down Expand Up @@ -78,6 +84,12 @@
</b-button>
</b-table-column>

<b-table-column v-if="can.sendFulfilmentEmails">
<b-button @click="openAntennaCriteriaMailSend(props.row)" class="button is-danger">
<span class="white"><font-awesome-icon :icon="['fa', 'envelope']" /></span>
</b-button>
</b-table-column>

</template>

<template slot="empty">
Expand All @@ -94,12 +106,15 @@ import { mapGetters } from 'vuex'
import moment from 'moment'
import AntennaCriteriaModal from './AntennaCriteriaModal.vue'
import AntennaCriteriaInfo from './AntennaCriteriaInfo.vue'
import AntennaCriteriaMail from './AntennaCriteriaMail.vue'
import AntennaCriteriaMailSend from './AntennaCriteriaMailSend.vue'
export default {
name: 'AntennaCriteriaCheck',
data () {
return {
bodies: [],
netcommies: [],
agorae: null,
selectedAgora: null,
showDetails: false,
Expand All @@ -109,9 +124,13 @@ export default {
summerUniversities: [],
isLoading: false,
isLoadingAgora: false,
permissions: [],
can: {
sendFulfilmentEmails: false
},
antennaCriteriaMapping: {
'contact': ['communication'],
'contact antenna': ['membersList', 'membershipFee'],
'contact antenna': ['communication', 'membersList', 'membershipFee'],
'antenna': ['communication', 'boardElection', 'membersList', 'membershipFee', 'events', 'agoraAttendance', 'developmentPlan', 'fulfilmentReport']
}
}
Expand All @@ -134,6 +153,8 @@ export default {
props: {
local: row,
agora: this.selectedAgora,
netcommies: this.netcommies,
permissions: this.permissions,
services: this.services,
showError: this.$root.showError,
showSuccess: this.$root.showSuccess,
Expand All @@ -155,6 +176,36 @@ export default {
}
})
},
openAntennaCriteriaMail () {
this.$buefy.modal.open({
component: AntennaCriteriaMail,
hasModalCard: true,
props: {
agora: this.selectedAgora,
mailComponents: this.mailComponents,
services: this.services,
showError: this.$root.showError,
showSuccess: this.$root.showSuccess,
router: this.$router
}
})
},
openAntennaCriteriaMailSend (row) {
this.$buefy.modal.open({
component: AntennaCriteriaMailSend,
hasModalCard: true,
props: {
local: row,
agora: this.selectedAgora,
mailComponents: this.mailComponents,
antennaCriteriaMapping: this.antennaCriteriaMapping,
services: this.services,
showError: this.$root.showError,
showSuccess: this.$root.showSuccess,
router: this.$router
}
})
},
toggleShowDetails () {
this.showDetails = !this.showDetails
},
Expand Down Expand Up @@ -229,9 +280,11 @@ export default {
})
const promises = []
promises.push(this.fetchNetcomAssignment())
promises.push(this.checkBoardCriterium())
promises.push(this.checkMembersListAndFeeCriteria())
promises.push(this.checkEventsCriterium())
promises.push(this.fetchMailComponents())
// The allSettled() command waits for all promises to be done, so it is also 'fine' if some of them fail
await Promise.allSettled(promises)
Expand Down Expand Up @@ -367,10 +420,49 @@ export default {
}).catch((err) => {
this.$root.showError('Could not fetch manual Antenna Criteria fulfilment', err)
})
},
async fetchNetcom () {
await this.axios.get(this.services['core'] + '/bodies?query=network%20commission').then(async (netcomBodyResponse) => {
await this.axios.get(this.services['core'] + '/bodies/' + netcomBodyResponse.data.data[0].id + '/members').then((netcomMembersResponse) => {
this.netcommies = netcomMembersResponse.data.data.map(netcommie => ({
user_id: netcommie.user_id,
first_name: netcommie.user.first_name,
email: netcommie.user.email
}))
this.netcommies.push({ 'user_id': 0, 'first_name': 'Not set', 'email': '' })
})
}).catch((err) => {
this.$root.showError('Could not fetch NetCom data', err)
})
},
async fetchNetcomAssignment () {
await this.axios.get(this.services['network'] + '/netcom').then(async (netcomResponse) => {
const netcomAssignment = netcomResponse.data.data
await this.fetchNetcom()
for (const body of this.bodies) {
const assignment = netcomAssignment.find(x => x.body_id === body.id)
this.$set(body, 'netcom', assignment !== undefined ? this.netcommies.find(x => x.user_id === assignment.netcom_id) : this.netcommies[this.netcommies.length - 1])
}
}).catch((err) => {
this.$root.showError('Could not fetch NetCom assignment', err)
})
},
async fetchMailComponents () {
await this.axios.get(this.services['network'] + '/mailComponent/' + this.selectedAgora.id).then((mailResponse) => {
this.mailComponents = mailResponse.data.data
}).catch((err) => {
this.$root.showError('Could not fetch mail components', err)
})
}
},
mounted () {
this.fetchAgorae()
this.axios.get(this.services['core'] + '/my_permissions').then((permissionResponse) => {
this.permissions = permissionResponse.data.data
this.can.sendFulfilmentEmails = this.permissions.some(permission => permission.combined.endsWith('manage_network:fulfilment_email'))
})
}
}
Expand Down
150 changes: 150 additions & 0 deletions src/views/network/AntennaCriteriaMail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<template>
<div class="modal-card" style="height: 75vh">
<header class="modal-card-head">
<p class="modal-card-title">Change fulfilment email text</p>
<button class="delete" aria-label="close" @click="$parent.close()" />
</header>

<section class="modal-card-body">
<div class="notification is-warning">
You can type the following to be replaced with the actual data:
<ul style="list-style: inside">
<li><em>{body_name}</em> - name of the body</li>
<li><em>{agora_name}</em> - name of the Agora</li>
<li><em>{netcom_name}</em> - name of the assigned NetCommie</li>
<li><em>{local_type}</em> - type of the Local</li>
<li><em>{antenna_criteria_amount}</em> - the amount of Antenna Criteria to fulfil</li>
</ul>
You can also use Markdown, here are some basic functions:
<ul style="list-style: inside">
<li>Placing * around a word makes it <em>italic</em></li>
<li>Placing ** around a word makes it <strong>bold</strong></li>
<li>A link can be made by typing it like "[text to display](url)"</li>
<li>Enters can be inserted by typing "&lt;br&gt;"</li>
<li>Placing "&lt;r&gt;XXX&lt;/r&gt;" around a word makes it <span style="color: red">red</span></li>
</ul>
</div>

<b-field label="Introduction text">
<b-input type="textarea" v-model="parts.introduction" />
</b-field>

<b-field label="Communication">
<b-input type="textarea" v-model="parts.communication" />
</b-field>

<b-field label="Board election">
<b-input type="textarea" v-model="parts.boardElection" />
</b-field>

<b-field label="Members list">
<b-input type="textarea" v-model="parts.membersList" />
</b-field>

<b-field label="Membership fee">
<b-input type="textarea" v-model="parts.membershipFee" />
</b-field>

<b-field label="Events">
<b-input type="textarea" v-model="parts.events" />
</b-field>

<b-field label="Agora attendance">
<b-input type="textarea" v-model="parts.agoraAttendance" />
</b-field>

<b-field label="Development plan">
<b-input type="textarea" v-model="parts.developmentPlan" />
</b-field>

<b-field label="Fulfilment Report">
<b-input type="textarea" v-model="parts.fulfilmentReport" />
</b-field>

<b-field label="Closing">
<b-input type="textarea" v-model="parts.closing" />
</b-field>

<hr />

<h2 class="subtitle">Preview of full mail</h2>
<span v-html="$options.filters.markdown(mail)" />

</section>

<footer class="modal-card-foot">
<button class="button is-primary" @click="save()">Save</button>
<button class="button" @click="$parent.close()">Cancel</button>
</footer>
</div>
</template>

<script>
export default {
name: 'AntennaCriteriaMail',
props: ['agora', 'mailComponents', 'permissions', 'services', 'showError', 'showSuccess', 'router'],
data () {
return {
parts: {
introduction: '',
communication: '',
boardElection: '',
membersList: '',
membershipFee: '',
events: '',
agoraAttendance: '',
developmentPlan: '',
fulfilmentReport: '',
closing: ''
}
}
},
methods: {
async setMailComponent (component, text) {
const data = {
'agora_id': this.agora.id,
'mail_component': component,
'text': text
}
await this.axios.put(
this.services['network'] + '/mailComponent',
data
).catch((err) => {
this.showError('Error saving mail component', err)
})
},
async save () {
const promises = []
for (const part in this.parts) {
const component = part.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase()
const original = this.mailComponents.find(c => c.mail_component === component)
if (original !== this.parts[part]) {
promises.push(this.setMailComponent(component, this.parts[part]))
}
}
await Promise.all(promises).then(() => {
this.showSuccess('Mail components updated.')
this.router.go(0)
}).catch((err) => {
this.showError('Something went wrong', err)
})
}
},
computed: {
mail () {
// Combine all the different items in parts, in order
const rawMail = Object.values(this.parts).join('<br><br>')
return rawMail.replaceAll('<r>', '<span style="color: red">').replaceAll('</r>', '</span>')
}
},
mounted () {
for (const component of this.mailComponents) {
const part = component.mail_component.replace(/ (\w)/g, (_, c) => c.toUpperCase())
this.parts[part] = component.text
}
}
}
</script>
Loading

0 comments on commit 6671d8d

Please sign in to comment.