Skip to content

Commit

Permalink
[SG-1950] -- MOHCD Calculator (#1519)
Browse files Browse the repository at this point in the history
* [SG-1950] -- MOHCD Calculator

SG-1950

Added a new module for creating custom pages and widgets. First page/widget is MOHCD Calculator embed and its settings page.
The setup will allow users to update content without having to update any config yml's, so users can update without having to update any code.

Instruction:
- Clear cache
- Config import
- Visit Calculator settings page in admin/config/system/sfgov-pages and go to the MOHCD calculator settings page.
- Update all the settings and select the page to embed on.
- Visit the test page via the link on the admin page
or
- Visit the embed page via the link on the admin page, after selecting a transaction node to embed on.

* Javascript linting and cleanup

* javascript linting and cleanup

* Forgot the embed template and to enable the module

* isset check to help remove some php warnings from the logs

* [SG-1950] -- Theming of calculator. Changed custom javascript into a drupal ajax form setup.

* Adjustment to mobile layout of calculator

* Width adjustment of the calculator

* Update to calculator setup and styling

* Responsive styling

* Set mohcd calculator permission to digital services. Also removed anonymous and authenticated permission to devel (anonymous permission for devel should definitely not be enabled)

* [SG-1950] -- Clean up of code

* Update to form to use numeric pad on mobile input

* Cleanup of how the earliest and latest years are found. Better validation messaging.

* Added ability to control the label and description of the calculator

* [SG-1950] -- Better translation for calculator label and description
  • Loading branch information
paboden authored Mar 22, 2023
1 parent 356238a commit cc1d0dc
Show file tree
Hide file tree
Showing 22 changed files with 819 additions and 5 deletions.
1 change: 1 addition & 0 deletions config/core.extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ module:
sfgov_migrate: 0
sfgov_moderation: 0
sfgov_news: 0
sfgov_pages: 0
sfgov_profiles: 0
sfgov_public_bodies: 0
sfgov_qless: 0
Expand Down
1 change: 0 additions & 1 deletion config/user.role.anonymous.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ is_admin: false
permissions:
- 'access comments'
- 'access content'
- 'access devel information'
- 'access protected page password screen'
- 'access site-wide contact form'
- 'search content'
Expand Down
1 change: 0 additions & 1 deletion config/user.role.authenticated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ permissions:
- 'access comments'
- 'access content'
- 'access contextual links'
- 'access devel information'
- 'access file entity browser pages'
- 'access form_file entity browser pages'
- 'access location_mailing entity browser pages'
Expand Down
2 changes: 2 additions & 0 deletions config/user.role.digital_services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies:
- redirect
- role_delegation
- scheduler
- sfgov_pages
- sfgov_qless
- sfgov_vaccine
- shortcut
Expand Down Expand Up @@ -511,3 +512,4 @@ permissions:
- 'view unpublished eck entities'
- 'view unpublished paragraphs'
- 'view video media revisions'
- 'administer mohcd pages'
1 change: 1 addition & 0 deletions scripts/custom/build-theme-assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ npm run -w sfgovpl build
npm run -w sfgov_vaccine build
npm run -w sfgov_search build
npm run -w sfgov_admin build
npm run -w sfgov_pages build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sfgov_amplitude.admin:
title: 'SF.Gov amplitude settings'
title: 'SF.Gov Amplitude settings'
description: 'Configure SF.Gov amplitude settings'
parent: system.admin_config_development
route_name: sfgov_amplitude.admin_settings
2 changes: 2 additions & 0 deletions web/modules/custom/sfgov_pages/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
dist
2 changes: 2 additions & 0 deletions web/modules/custom/sfgov_pages/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module.exports = {
}
17 changes: 17 additions & 0 deletions web/modules/custom/sfgov_pages/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"scripts": {
"build": "npm run build-css",
"build-css": "postcss --verbose -d dist/css 'src/**/css/*.css'",
"watch-css": "npm run build-css -- --watch",
"watch": "npm run watch-css"
},
"devDependencies": {
"@csstools/postcss-sass": "^5.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.18",
"postcss-cli": "^10.0.0",
"postcss-import": "^15.0.0",
"postcss-scss": "^4.0.5",
"prettier": "^2.5.1"
}
}
5 changes: 5 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: 'SFGov Pages'
type: module
description: 'A module that contains source and configuration for custom built pages on sf.gov'
core_version_requirement: ^8 || ^9
package: 'SF Gov'
5 changes: 5 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mohcd_calculator:
css:
component:
# src/mohcd/css/mohcd-calculator.css: { }
dist/css/mohcd-calculator.css: { }
11 changes: 11 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.links.menu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
sfgov_pages.admin_config_sfgov_pages:
route_name: sfgov_pages.admin_config_sfgov_pages
parent: system.admin_config_development
title: 'SF.Gov Pages'
description: 'Configure custom SF.Gov pages and widgets, like the MOHCD calculator.'

sfgov_pages.mohcd_bmr_valuation_calculator_settings_form:
route_name: sfgov_pages.mohcd_bmr_valuation_calculator_settings_form
parent: sfgov_pages.admin_config_sfgov_pages
title: 'SF.Gov MOHCD BMR calculator'
description: 'Configure settings and select the page to embed the MOHCD calculator.'
30 changes: 30 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/**
* Implements hook_theme().
*/
function sfgov_pages_theme($existing, $type, $theme, $path) {
return [
'input__textfield__mohcd_calculator_form__bmrcalculator_purchaseprice' => [
'base hook' => 'input',
],
];
}

/**
* Implements hook_preprocess_node().
*/
function sfgov_pages_preprocess_node__transaction__full(&$variables) {
$variables['#cache']['contexts'][] = 'url.query_args';
$node = $variables['node'];

// Create the embed mchod calculator embed variable.
$nid_to_embed_on = \Drupal::state()->get('sfgov_pages_mohcd_embed_page');
$variables['mohcd_embed'] = NULL;

// If current transaction node matches the mohcd embed node...
if ($node->id() == $nid_to_embed_on) {
$variables['mohcd_embed'] = \Drupal::formBuilder()->getForm('\Drupal\sfgov_pages\mohcd\Form\CalculatorForm');
}
}

2 changes: 2 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.permissions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
administer mohcd pages:
title: 'Administer mohcd pages'
23 changes: 23 additions & 0 deletions web/modules/custom/sfgov_pages/sfgov_pages.routing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
sfgov_pages.admin_config_sfgov_pages:
path: '/admin/config/system/sfgov-pages'
defaults:
_controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
_title: 'SF.Gov pages'
requirements:
_permission: 'access administration pages'

sfgov_pages.mohcd_bmr_valuation_calculator_settings_form:
path: '/admin/config/system/sfgov-pages/mohcd-bmr-valuation-calculator-settings'
defaults:
_form: '\Drupal\sfgov_pages\mohcd\Form\CalculatorSettingsForm'
_title: 'SF.Gov MOHCD BMR valuation calculator settings'
requirements:
_permission: 'administer mohcd pages'

sfgov_pages.mohcd_bmr_valuation_calculator:
path: '/mohcd/calculator'
defaults:
_form: '\Drupal\sfgov_pages\mohcd\Form\CalculatorForm'
_title: 'MOHCD Calculator'
requirements:
_permission: 'administer mohcd pages'
238 changes: 238 additions & 0 deletions web/modules/custom/sfgov_pages/src/mohcd/Form/CalculatorForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<?php

namespace Drupal\sfgov_pages\mohcd\Form;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use NumberFormatter;

/**
* MOHCD Calculator form.
*/
class CalculatorForm extends FormBase {

/**
* {@inheritdoc}
*/
public function getFormId() {
return 'mohcd_calculator_form';
}

/**
* Return the calculator label
*/
public function getLabel() {
return \Drupal::state()->get('sfgov_pages_mohcd_label') ?? t('Calculate');
}

/**
* Return the calculator description/help text
*/
public function getDescription() {
return \Drupal::state()->get('sfgov_pages_mohcd_description') ?? t('Your purchase information can be found in the Promissory Note and closing documents.');
}

/**
* Return the current AMI
*/
public function getCurrentYearAMI() {
return \Drupal::state()->get('sfgov_pages_mohcd_currentYearAMI');
}

/**
* Get the complete year|AMI list
*/
public function getYearAMIList(): array {
$options = [];
$yearAMI = \Drupal::state()->get('sfgov_pages_mohcd_yearAMI');
$yearAMIArray = preg_split('/\r\n|\r|\n/', $yearAMI);
foreach($yearAMIArray as $yearAMI) {
$value = explode("|", $yearAMI);
$options[trim($value[0])] = trim($value[1]);
}
return $options;
}

/**
* Get the least recent (earliest) year from the AMI list
*/
public function getEarliestYear() {
$options = $this->getYearAMIList();
$years = array_flip($options);
return min($years);
}

/**
* Get the most recent (latest) year from the AMI list
*/
public function getLatestYear() {
$options = $this->getYearAMIList();
$years = array_flip($options);
return max($years);
}

/**
* Return the AMI value for the given year.
*/
public function getYearAMI($year) {
$options = $this->getYearAMIList();
return $options[$year] ?? FALSE;
}

/**
* Build the calculator.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$currentYearAMI = $this->getCurrentYearAMI();

$form['#tree'] = TRUE;

$form['bmrCalculator'] = array(
'#type' => 'fieldset',
'#title' => t('@label', ['@label' => $this->getLabel()]),
'#description' => t('@description', ['@description' => $this->getDescription()]),
'#description_display' => 'before',
);

$form['bmrCalculator']['purchasePrice'] = [
'#id' => 'purchasePrice',
'#type' => 'textfield',
'#title' => t('Purchase price'),
'#prefix' => '<div id="purchasePriceWrapper">',
'#field_suffix' => '<div id="purchasePriceError"></div>',
'#suffix' => '</div>',
'#attributes' => [
'inputmode' => 'numeric',
'pattern' => '[0-9]*',
'minlength' => '4',
'maxlength' => '12',
],
];

$form['bmrCalculator']['purchaseYear'] = [
'#id' => 'purchaseYear',
'#type' => 'textfield',
'#title' => t('Purchase year'),
'#description' => t('Enter a year between @firstyear and @latestyear', [
'@firstyear' => $this->getEarliestYear(),
'@latestyear' => $this->getLatestYear()
]),
'#prefix' => '<div id="purchaseYearWrapper">',
'#field_suffix' => '<div id="purchaseYearError"></div>',
'#suffix' => '</div>',
'#attributes' => [
'inputmode' => 'numeric',
'minlength' => '4',
'maxlength' => '4',
'pattern' => '[0-9]*',
'min' => $this->getEarliestYear(),
'max' => $this->getLatestYear(),
],
];

$form['bmrCalculator']['btnCalc'] = [
'#id' => 'btnCalc',
'#type' => 'button',
'#value' => t('Calculate'),
'#attributes' => [
'class' => ['button button-primary'],
],
'#ajax' => [
'callback' => '::calculateBMRValuation',
'effect' => 'fade',
'progress' => array(
'type' => 'none',
'message' => t('Calculating...'),
),
],
];

$form['bmrCalculator']['bmrValuation'] = [
'#type' => 'markup',
'#markup'=> '<div id="bmrValuation"></div> ',
];

$form['#attached']['library'][] = 'sfgov_pages/mohcd_calculator';
$form['#attached']['drupalSettings']['sfgov']['mohcd']['calculator']['currentYearAMI'] = $currentYearAMI;

$form['#attributes']['class'][] = 'sfgov-section__content';

$form['#cache']['max-age'] = 0;

return $form;
}

/**
* Calculate the BMR Value and display on the form.
*/
public function calculateBMRValuation(array $form, FormStateInterface $form_state): AjaxResponse {
$ajax_response = new AjaxResponse();

$has_error = FALSE;
$currentYearAMI = $this->getCurrentYearAMI();
$values = $form_state->getValues();
$purchasePrice = $values['bmrCalculator']['purchasePrice'];
$purchaseYear = (int) $values['bmrCalculator']['purchaseYear'];
$purchaseYearAMI = $this->getYearAMI($purchaseYear);

// Assert the purchasePrice is valid
$ajax_response->addCommand(new HtmlCommand('#purchasePriceError', ''));
if (!$purchasePrice || !is_numeric($purchasePrice)) {
$has_error = TRUE;
$ajax_response->addCommand(new HtmlCommand('#purchasePriceError', t("Price must be in numbers only.")));
}

// Assert the purchaseYear is valid
$ajax_response->addCommand(new HtmlCommand('#purchaseYearError', ''));
if (!$purchaseYearAMI || !is_numeric($purchaseYearAMI)) {
$has_error = TRUE;
$ajax_response->addCommand(new HtmlCommand('#purchaseYearError ', t("Year must be 4 numbers.")));
}

// Assert the purchaseYear must be between the earliest and latest years. ##UNCOMMENT BELOW TO TRIGGER VALIDATION.
// if ($purchaseYear && is_numeric($purchaseYear) && ($purchaseYear < $this->getEarliestYear() || $purchaseYear > $this->getLatestYear())) {
// $has_error = TRUE;
// $ajax_response->addCommand(new HtmlCommand('#purchaseYearError ', t('Year must be between @firstyear and @latestyear', [
// '@firstyear' => $this->getEarliestYear(),
// '@latestyear' => $this->getLatestYear()
// ])));
// }

if (!$has_error) {
$value = (int) round($purchasePrice + ($purchasePrice * (($currentYearAMI - $purchaseYearAMI) / $purchaseYearAMI)));

$formatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
$value = $formatter->formatCurrency($value, 'USD');

$text = [];

$text['label'] = [
'#type' => 'markup',
'#prefix' => '<div id="bmrValuationLabel">',
'#markup' => t('Your current BMR valuation:'),
'#suffix' => '</div>',
];

$text['value'] = [
'#type' => 'markup',
'#prefix' => '<div id="bmrValuationResults">',
'#markup' => $value,
'#suffix' => '</div>',
];

$ajax_response->addCommand(new HtmlCommand('#bmrValuation', \Drupal::service('renderer')->render($text)));
} else {
$ajax_response->addCommand(new HtmlCommand('#bmrValuation', ''));
}

return $ajax_response;
}

// no validation handled in src/mohcd/js/mohcd-calculator.js
public function validateForm(array &$form, FormStateInterface $form_state) {}

// no submission, calculation handled in src/mohcd/js/mohcd-calculator.js
public function submitForm(array &$form, FormStateInterface $form_state) {}
}
Loading

1 comment on commit cc1d0dc

@aekong
Copy link
Collaborator

@aekong aekong commented on cc1d0dc Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visit Site

Created multidev environment ci-18192 for sfgov.

Please sign in to comment.