Skip to content

Commit

Permalink
#2337: New feature flag for moving Grant Details from a modal to an i…
Browse files Browse the repository at this point in the history
…ndependent page (#2338)

* fix: copy GrantDetails.vue and make GrantDetailsLegacy.vue for the old modal

* fix: fixing a link path

* fix: add initial feature flag

* fix: use existing modal GrantDetails only if the new feature flag is turned off

* fix: rename modal control to GrantDetailsLegacy
  • Loading branch information
adele-usdr authored Dec 15, 2023
1 parent bf81d0f commit 0666d17
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/client/public/deploy-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ window.APP_CONFIG.overrideFeatureFlag = (flagName, overrideValue) => {
window.APP_CONFIG.featureFlags = {
myProfileEnabled: true,
newTerminologyEnabled: true,
newGrantsDetailPageEnabled: false,
};
11 changes: 7 additions & 4 deletions packages/client/src/components/GrantsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@
<div class="my-1 rounded py-1 px-2 page-item">{{ totalRows }} total grant{{ totalRows == 1 ? '' : 's' }}</div>
</b-col>
</b-row>
<GrantDetails :selected-grant.sync="selectedGrant" />
<GrantDetailsLegacy v-if="!newGrantsDetailPageEnabled" :selected-grant.sync="selectedGrant" />
</section>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';
import { newTerminologyEnabled } from '@/helpers/featureFlags';
import { newTerminologyEnabled, newGrantsDetailPageEnabled } from '@/helpers/featureFlags';
import { titleize } from '../helpers/form-helpers';
import GrantDetails from './Modals/GrantDetails.vue';
import GrantDetailsLegacy from './Modals/GrantDetailsLegacy.vue';
import SearchPanel from './Modals/SearchPanel.vue';
import SavedSearchPanel from './Modals/SavedSearchPanel.vue';
import SearchFilter from './SearchFilter.vue';
export default {
components: {
GrantDetails, SearchPanel, SavedSearchPanel, SearchFilter,
GrantDetailsLegacy, SearchPanel, SavedSearchPanel, SearchFilter,
},
props: {
showInterested: Boolean,
Expand Down Expand Up @@ -208,6 +208,9 @@ export default {
searchFilters() {
return this.activeFilters;
},
newGrantsDetailPageEnabled() {
return newGrantsDetailPageEnabled();
},
},
watch: {
selectedAgency() {
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/helpers/featureFlags/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ export function myProfileEnabled() {
export function newTerminologyEnabled() {
return getFeatureFlags().newTerminologyEnabled === true;
}

export function newGrantsDetailPageEnabled() {
return getFeatureFlags().newGrantsDetailPageEnabled === true;
}
11 changes: 7 additions & 4 deletions packages/client/src/views/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
</template>
</b-table>
</b-card>
<GrantDetails :selected-grant.sync="selectedGrant" />
<GrantDetailsLegacy v-if="!newGrantsDetailPageEnabled" :selected-grant.sync="selectedGrant" />
</section>
</template>

Expand Down Expand Up @@ -144,11 +144,11 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import resizableTableMixin from '@/mixin/resizableTable';
import GrantDetails from '@/components/Modals/GrantDetails.vue';
import { newTerminologyEnabled } from '@/helpers/featureFlags';
import GrantDetailsLegacy from '@/components/Modals/GrantDetailsLegacy.vue';
import { newTerminologyEnabled, newGrantsDetailPageEnabled } from '@/helpers/featureFlags';

export default {
components: { GrantDetails },
components: { GrantDetailsLegacy },
data() {
return {
dateColors: [],
Expand Down Expand Up @@ -366,6 +366,9 @@ export default {
newTerminologyEnabled() {
return newTerminologyEnabled();
},
newGrantsDetailPageEnabled() {
return newGrantsDetailPageEnabled();
},
},
watch: {
async selectedTeam() {
Expand Down
275 changes: 275 additions & 0 deletions packages/client/src/views/GrantDetails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
<!-- eslint-disable max-len -->
<template>
<b-modal v-model="showDialog" ok-only :title="selectedGrant && selectedGrant.grant_number"
@hide="resetSelectedGrant" scrollable size="xl" ok-title="Close">
<div v-if="selectedGrant">
<b-row class="mb-3 d-flex align-items-baseline">
<b-col cols="8">
<h1 class="mb-0 h2">{{ selectedGrant.title }}</h1>
</b-col>
<b-col cols="4" class="text-right">
<b-button :href="`https://www.grants.gov/web/grants/view-opportunity.html?oppId=${selectedGrant.grant_id}`"
target="_blank" rel="noopener noreferrer" variant="primary">
<b-icon icon="box-arrow-up-right" aria-hidden="true" class="mr-2"></b-icon>View on Grants.gov
</b-button>
</b-col>
</b-row>
<p><span class="data-label">Valid from:</span> {{ new
Date(selectedGrant.open_date).toLocaleDateString('en-US', { timeZone: 'UTC' })
}}-{{ new
Date(selectedGrant.close_date).toLocaleDateString('en-US', { timeZone: 'UTC' })
}}</p>
<div v-for="field in dialogFields" :key="field">
<p><span class="data-label">{{ titleize(field) }}:</span> {{ selectedGrant[field] }}</p>
</div>
<p class="data-label">Description:</p>
<div style="max-height: 170px; overflow-y: scroll">
<div style="white-space: pre-line" v-html="selectedGrant.description"></div>
</div>
<br />
<b-row class="ml-2 mb-2 d-flex align-items-baseline">
<h2 class="h4">{{newTerminologyEnabled ? 'Team': 'Agency'}} Status</h2>
<b-col class="text-right">
<b-row v-if="!interested">
<b-col cols="9">
<b-form-select v-model="selectedInterestedCode">
<b-form-select-option-group label="Interested">
<b-form-select-option v-for="code in interestedCodes.interested" :key="code.id" :value="code.id">
{{ code.name }}</b-form-select-option>
</b-form-select-option-group>
<b-form-select-option-group label="Applied">
<b-form-select-option v-for="code in interestedCodes.result" :key="code.id" :value="code.id">
{{ code.name }}</b-form-select-option>
</b-form-select-option-group>
<b-form-select-option-group label="Not Applying">
<b-form-select-option v-for="code in interestedCodes.rejections" :key="code.id" :value="code.id">
{{ code.name }}</b-form-select-option>
</b-form-select-option-group>
</b-form-select>
</b-col>
<b-button variant="outline-primary" @click="markGrantAsInterested">Submit</b-button>
</b-row>
<b-row v-if="interested && interested.interested_status_code !== 'Rejection'&& shouldShowSpocButton">
<b-col>
<b-button variant="primary" @click="generateSpoc">Generate SPOC</b-button>
</b-col>
</b-row>
</b-col>
</b-row>
<br />
<b-table :items="selectedGrant.interested_agencies" :fields="interestedAgenciesFields">
<template #cell(actions)="row">
<b-row
v-if="(String(row.item.agency_id) === selectedAgencyId) || isAbleToUnmark(row.item.agency_id)">
<b-button variant="outline-danger" class="mr-1 border-0" size="sm" @click="unmarkGrantAsInterested(row)">
<b-icon icon="trash-fill" aria-hidden="true"></b-icon>
</b-button>
</b-row>
</template>
</b-table>
<br />
<b-row class="ml-2 mb-2 d-flex align-items-baseline">
<h2 class="h4">Assigned {{newTerminologyEnabled ? 'Teams': 'Agencies'}}</h2>
<v-select v-model="selectedAgencies" :options="agencies" :multiple="true" :close-on-select="false"
:clear-on-select="false" :placeholder="`Select ${newTerminologyEnabled ? 'teams': 'agencies'}`" label="name" track-by="id"
style="width: 300px; margin: 0 16px;" :show-labels="false"
>
</v-select>
<b-button variant="outline-primary" @click="assignAgenciesToGrant">Assign</b-button>
</b-row>
<b-table :items="assignedAgencies" :fields="assignedAgenciesFields">
<template #cell(actions)="row">
<b-button variant="outline-danger" class="mr-1 border-0" size="sm" @click="unassignAgenciesToGrant(row)">
<b-icon icon="trash-fill" aria-hidden="true"></b-icon>
</b-button>
</template>
</b-table>
</div>
</b-modal>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';
import { debounce } from 'lodash';
import { newTerminologyEnabled } from '@/helpers/featureFlags';
import { titleize } from '../helpers/form-helpers';

export default {
props: {
selectedGrant: Object,
},
data() {
return {
showDialog: false,
dialogFields: ['grant_id', 'agency_code', 'award_ceiling', 'cfda_list', 'opportunity_category', 'bill'],
orderBy: '',
interestedAgenciesFields: [
{
key: 'agency_name',
label: `${newTerminologyEnabled ? 'Team' : 'Agency'}`,
},
{
key: 'agency_abbreviation',
label: 'Abbreviation',
},
{
label: 'Name',
key: 'user_name',
},
{
label: 'Email',
key: 'user_email',
},
{
label: 'Interested Code',
key: 'interested_code_name',
},
{
key: 'actions',
label: 'Actions',
},
],
assignedAgenciesFields: [
{
key: 'name',
},
{
key: 'abbreviation',
label: 'Abbreviation',
},
{
key: 'created_at',
},
{
key: 'actions',
label: 'Actions',
},
],
assignedAgencies: [],
selectedAgencies: [],
selectedInterestedCode: null,
searchInput: null,
debouncedSearchInput: null,
};
},
mounted() {
},
computed: {
...mapGetters({
agency: 'users/agency',
selectedAgencyId: 'users/selectedAgencyId',
agencies: 'agencies/agencies',
currentTenant: 'users/currentTenant',
users: 'users/users',
interestedCodes: 'grants/interestedCodes',
loggedInUser: 'users/loggedInUser',
selectedAgency: 'users/selectedAgency',
}),
alreadyViewed() {
if (!this.selectedGrant) {
return false;
}
return this.selectedGrant.viewed_by_agencies.find(
(viewed) => viewed.agency_id.toString() === this.selectedAgencyId,
);
},
shouldShowSpocButton() {
return this.currentTenant.uses_spoc_process;
},
interested() {
if (!this.selectedGrant) {
return undefined;
}
return this.selectedGrant.interested_agencies.find(
(interested) => interested.agency_id.toString() === this.selectedAgencyId,
);
},
newTerminologyEnabled() {
return newTerminologyEnabled();
},
},
watch: {
async selectedGrant() {
this.showDialog = Boolean(this.selectedGrant);
if (this.selectedGrant) {
this.fetchAgencies();
if (!this.alreadyViewed) {
try {
await this.markGrantAsViewed();
} catch (e) {
console.log(e);
}
}
this.assignedAgencies = await this.getGrantAssignedAgencies({ grantId: this.selectedGrant.grant_id });
}
},
},
methods: {
...mapActions({
markGrantAsViewedAction: 'grants/markGrantAsViewed',
generateGrantForm: 'grants/generateGrantForm',
markGrantAsInterestedAction: 'grants/markGrantAsInterested',
unmarkGrantAsInterestedAction: 'grants/unmarkGrantAsInterested',
getInterestedAgencies: 'grants/getInterestedAgencies',
getGrantAssignedAgencies: 'grants/getGrantAssignedAgencies',
assignAgenciesToGrantAction: 'grants/assignAgenciesToGrant',
unassignAgenciesToGrantAction: 'grants/unassignAgenciesToGrant',
fetchUsers: 'users/fetchUsers',
fetchAgencies: 'agencies/fetchAgencies',
}),
titleize,
debounceSearchInput: debounce(function bounce(newVal) {
this.debouncedSearchInput = newVal;
}, 500),
async markGrantAsViewed() {
await this.markGrantAsViewedAction({ grantId: this.selectedGrant.grant_id, agencyId: this.selectedAgencyId });
},
async markGrantAsInterested() {
if (this.selectedInterestedCode !== null) {
await this.markGrantAsInterestedAction({
grantId: this.selectedGrant.grant_id,
agencyId: this.selectedAgencyId,
interestedCode: this.selectedInterestedCode,
});
}
},
async unmarkGrantAsInterested(row) {
await this.unmarkGrantAsInterestedAction({
grantId: this.selectedGrant.grant_id,
agencyIds: [row.item.agency_id],
interestedCode: this.selectedInterestedCode,
});
this.selectedGrant.interested_agencies = await this.getInterestedAgencies({ grantId: this.selectedGrant.grant_id });
},
async assignAgenciesToGrant() {
const agencyIds = this.selectedAgencies.map((agency) => agency.id);
await this.assignAgenciesToGrantAction({
grantId: this.selectedGrant.grant_id,
agencyIds,
});
this.selectedAgencies = [];
this.assignedAgencies = await this.getGrantAssignedAgencies({ grantId: this.selectedGrant.grant_id });
},
async unassignAgenciesToGrant(row) {
await this.unassignAgenciesToGrantAction({
grantId: this.selectedGrant.grant_id,
agencyIds: [row.item.id],
});
this.assignedAgencies = await this.getGrantAssignedAgencies({ grantId: this.selectedGrant.grant_id });
},
async generateSpoc() {
await this.generateGrantForm({
grantId: this.selectedGrant.grant_id,
});
},
isAbleToUnmark(agencyId) {
return this.agencies.some((agency) => agency.id === agencyId);
},
resetSelectedGrant() {
this.$emit('update:selectedGrant', null);
this.assignedAgencies = [];
this.selectedAgencies = [];
},
},
};
</script>
10 changes: 7 additions & 3 deletions packages/client/src/views/RecentActivity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
aria-controls="grants-table" />
<b-button class="ml-2" variant="outline-primary disabled">{{ grantsInterested.length }} of {{ totalRows }}</b-button>
</b-row>
<GrantDetails :selected-grant.sync="selectedGrant" />
<GrantDetailsLegacy v-if="!newGrantsDetailPageEnabled" :selected-grant.sync="selectedGrant" />
</section>
</template>
<style scoped>
Expand All @@ -71,11 +71,12 @@

<script>
import { mapActions, mapGetters } from 'vuex';
import { newGrantsDetailPageEnabled } from '@/helpers/featureFlags';
import resizableTableMixin from '@/mixin/resizableTable';
import GrantDetails from '@/components/Modals/GrantDetails.vue';
import GrantDetailsLegacy from '@/components/Modals/GrantDetailsLegacy.vue';
export default {
components: { GrantDetails },
components: { GrantDetailsLegacy },
data() {
return {
perPage: 10,
Expand Down Expand Up @@ -156,6 +157,9 @@ export default {
totalRows() {
return this.totalInterestedGrants;
},
newGrantsDetailPageEnabled() {
return newGrantsDetailPageEnabled();
},
},
watch: {
async selectedGrant() {
Expand Down
Loading

0 comments on commit 0666d17

Please sign in to comment.