diff --git a/.eslintrc.js b/.eslintrc.js index b679b6fa9..84311232b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,4 +16,12 @@ module.exports = { rules: { "prettier/prettier": "error", }, + settings: { + react: { + // Version checking of react. Default is "detect", which + // fails at the root directory, since react is not installed + // here. Providing a dummy number supresses the warning + version: "16.0", + }, + }, }; diff --git a/app/services/account-service.js b/app/services/account-service.js index 0b269866d..199a246e4 100644 --- a/app/services/account-service.js +++ b/app/services/account-service.js @@ -402,7 +402,7 @@ const setPermissions = async (userId, permissionName, value) => { try { // do a tiny bit of sanity checking on our input var booleanValue = Boolean(value); - const updateSql = `update login set ${permissionName}=$1} where id = ${userId};`; + const updateSql = `update login set ${permissionName}=$1 where id = ${userId};`; await pool.query(updateSql, [booleanValue]); return { success: true, diff --git a/app/services/stakeholder-best-service.js b/app/services/stakeholder-best-service.js index d56fc8ada..a9b94e653 100644 --- a/app/services/stakeholder-best-service.js +++ b/app/services/stakeholder-best-service.js @@ -12,6 +12,9 @@ record has been approved, it will be the most recent version (i.e., have If you make changes to the database structure, be sure to update these methods as well as the corresponding methods in the stakeholder-service.js. +You can search by max/min lat, lng bounds or by a center and radius(distance), +with bounds taking precedence. + */ const booleanEitherClause = (columnName, value) => { @@ -27,13 +30,16 @@ const search = async ({ latitude, longitude, distance, + maxLat, + maxLng, + minLat, + minLng, isInactive, verificationStatusId, tenantId, }) => { const locationClause = buildLocationClause(latitude, longitude); const categoryClause = buildCTEClause(categoryIds, ""); - const sql = `${categoryClause} select s.id, s.name, s.address_1, s.address_2, s.city, s.state, s.zip, s.phone, s.latitude, s.longitude, s.website, s.notes, @@ -62,16 +68,20 @@ const search = async ({ s.donation_delivery_instructions, s.donation_notes, s.covid_notes, s.category_notes, s.eligibility_notes, s.food_types, s.languages, s.v_name, s.v_categories, s.v_address, s.v_phone, s.v_email, - s.v_hours, s.verification_status_id, s.inactive_temporary, + s.v_hours, s.v_food_types, s.verification_status_id, s.inactive_temporary, array_to_json(s.hours) as hours, s.category_ids, s.neighborhood_id, s.is_verified, + s.food_bakery, s.food_dry_goods, s.food_produce, + s.food_dairy, s.food_prepared, s.food_meat, ${locationClause ? `${locationClause} AS distance,` : ""} ${buildLoginSelectsClause()} from stakeholder_set as s ${buildLoginJoinsClause()} - where s.tenant_id = ${tenantId} + where s.tenant_id = ${tenantId} ${ - Number(distance) && locationClause + maxLat && maxLng && minLat && minLng + ? buildBounds({ maxLat, maxLng, minLat, minLng }) + : Number(distance) && locationClause ? `AND ${locationClause} < ${distance}` : "" } @@ -83,7 +93,6 @@ const search = async ({ } order by distance `; - // console.log(sql); let stakeholders = []; let categoriesResults = []; var stakeholderResult, stakeholder_ids; @@ -173,6 +182,12 @@ const search = async ({ covidNotes: row.covid_notes || "", categoryNotes: row.category_notes || "", eligibilityNotes: row.eligibility_notes || "", + foodBakery: row.food_bakery, + foodDryGoods: row.food_dry_goods, + foodProduce: row.food_produce, + foodDairy: row.food_dairy, + foodPrepared: row.food_prepared, + foodMeat: row.food_meat, foodTypes: row.food_types || "", languages: row.languages || "", confirmedName: row.v_name, @@ -181,6 +196,7 @@ const search = async ({ confirmedPhone: row.v_phone, confirmedEmail: row.v_email, confirmedHours: row.v_hours, + confirmedFoodTypes: row.v_food_types, verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, @@ -224,8 +240,10 @@ const selectById = async (id) => { s.donation_delivery_instructions, s.donation_notes, s.covid_notes, s.category_notes, s.eligibility_notes, s.food_types, s.languages, s.v_name, s.v_categories, s.v_address, s.v_phone, s.v_email, - s.v_hours, s.verification_status_id, s.inactive_temporary, + s.v_hours, s.v_food_types, s.verification_status_id, s.inactive_temporary, s.neighborhood_id, s.is_verified, + s.food_bakery, s.food_dry_goods, s.food_produce, + s.food_dairy, s.food_prepared, s.food_meat, ${buildLoginSelectsClause()} from stakeholder_best s ${buildLoginJoinsClause()} @@ -296,6 +314,12 @@ const selectById = async (id) => { covidNotes: row.covid_notes || "", categoryNotes: row.category_notes || "", eligibilityNotes: row.eligibility_notes || "", + foodBakery: row.food_bakery, + foodDryGoods: row.food_dry_goods, + foodProduce: row.food_produce, + foodDairy: row.food_dairy, + foodPrepared: row.food_prepared, + foodMeat: row.food_meat, foodTypes: row.food_types || "", languages: row.languages || "", confirmedName: row.v_name, @@ -304,6 +328,7 @@ const selectById = async (id) => { confirmedPhone: row.v_phone, confirmedEmail: row.v_email, confirmedHours: row.v_hours, + confirmedFoodTypes: row.v_food_types, verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, @@ -349,6 +374,13 @@ const buildLocationClause = (latitude, longitude) => { return locationClause; }; +const buildBounds = ({ maxLat, maxLng, minLat, minLng }) => { + return ` + AND s.latitude BETWEEN ${minLat} AND ${maxLat} + AND s.longitude BETWEEN ${minLng} AND ${maxLng} + `; +}; + const buildLoginJoinsClause = () => { return ` left join login L1 on s.created_login_id = L1.id diff --git a/app/services/stakeholder-log-service.js b/app/services/stakeholder-log-service.js index dfa9d7bcc..308b7151c 100644 --- a/app/services/stakeholder-log-service.js +++ b/app/services/stakeholder-log-service.js @@ -31,8 +31,10 @@ const selectById = async (id) => { s.donation_delivery_instructions, s.donation_notes, s.covid_notes, s.category_notes, s.eligibility_notes, s.food_types, s.languages, s.v_name, s.v_categories, s.v_address, s.v_phone, s.v_email, - s.v_hours, s.verification_status_id, s.inactive_temporary, + s.v_hours, s.v_food_types, s.verification_status_id, s.inactive_temporary, s.neighborhood_id, + s.food_bakery, s.food_dry_goods, s.food_produce, + s.food_dairy, s.food_prepared, s.food_meat, ${buildLoginSelectsClause()} from stakeholder_log s ${buildLoginJoinsClause()} @@ -107,11 +109,18 @@ const selectById = async (id) => { confirmedPhone: row.v_phone, confirmedEmail: row.v_email, confirmedHours: row.v_hours, + confirmedFoodTypes: row.v_food_types, verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, neighborhoodName: row.neighborhood_name, completeCriticalPercent: row.complete_critical_percent, + foodBakery: row.food_bakery, + foodDryGoods: row.food_dry_goods, + foodProduce: row.food_produce, + foodDairy: row.food_dairy, + foodPrepared: row.food_prepared, + foodMeat: row.food_meat, }); }); diff --git a/app/services/stakeholder-service.js b/app/services/stakeholder-service.js index 694a0d63b..19254c574 100644 --- a/app/services/stakeholder-service.js +++ b/app/services/stakeholder-service.js @@ -86,10 +86,12 @@ const search = async ({ as claimed_date, s.claimed_login_id, s.requirements, s.admin_notes, s.inactive, s.email, s.covid_notes, s.v_name, s.v_categories, s.v_address, s.v_phone, s.v_email, - s.v_hours, s.verification_status_id, s.inactive_temporary, + s.v_hours, s.v_food_types, s.verification_status_id, s.inactive_temporary, s.neighborhood_id, n.name as neighborhood_name, s.complete_critical_percent, ${locationClause ? `${locationClause} AS distance,` : ""} + s.food_bakery, s.food_dry_goods, s.food_produce, + s.food_dairy, s.food_prepared, s.food_meat, ${buildLoginSelectsClause()} from stakeholder_set as s left outer join neighborhood n on s.neighborhood_id = n.id @@ -199,11 +201,18 @@ const search = async ({ confirmedPhone: row.v_phone, confirmedEmail: row.v_email, confirmedHours: row.v_hours, + confirmedFoodTypes: row.v_food_types, verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, neighborhoodName: row.neighborhood_name, completeCriticalPercent: row.complete_critical_percent, + foodBakery: row.food_bakery, + foodDryGoods: row.food_dry_goods, + foodProduce: row.food_produce, + foodDairy: row.food_dairy, + foodPrepared: row.food_prepared, + foodMeat: row.food_meat, }); }); @@ -249,8 +258,10 @@ const selectById = async (id) => { s.donation_delivery_instructions, s.donation_notes, s.covid_notes, s.category_notes, s.eligibility_notes, s.food_types, s.languages, s.v_name, s.v_categories, s.v_address, s.v_phone, s.v_email, - s.v_hours, s.verification_status_id, s.inactive_temporary, + s.v_hours, s.v_food_types, s.verification_status_id, s.inactive_temporary, s.neighborhood_id, + s.food_bakery, s.food_dry_goods, s.food_produce, + s.food_dairy, s.food_prepared, s.food_meat, ${buildLoginSelectsClause()} from stakeholder s ${buildLoginJoinsClause()} @@ -329,9 +340,16 @@ const selectById = async (id) => { confirmedPhone: row.v_phone, confirmedEmail: row.v_email, confirmedHours: row.v_hours, + confirmedFoodTypes: row.v_food_types, verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, + foodBakery: row.food_bakery, + foodDryGoods: row.food_dry_goods, + foodProduce: row.food_produce, + foodDairy: row.food_dairy, + foodPrepared: row.food_prepared, + foodMeat: row.food_meat, }; // Don't have a distance, since we didn't specify origin @@ -541,8 +559,15 @@ const insert = async (model) => { confirmedPhone, confirmedEmail, confirmedHours, + confirmedFoodTypes, verificationStatusId, inactiveTemporary, + foodBakery, + foodDryGoods, + foodProduce, + foodDairy, + foodPrepared, + foodMeat, } = model; try { let hoursSqlValues = hours.length @@ -562,66 +587,71 @@ const insert = async (model) => { // (ARRAY['(2,Wed,13:02,13:04)', '(3,Thu,07:00,08:00)'])::stakeholder_hours[]); --array of stakeholder_hours // objects, which are defined as a postgres type (see repo file for more detail on this type). const invokeSprocSql = `CALL create_stakeholder( - ${toSqlNumeric(0)}::INT, - ${toSqlNumeric(tenantId)}::INT, - ${toSqlString(name)}::VARCHAR, ${toSqlString( - address1 - )}::VARCHAR, ${toSqlString(address2)}::VARCHAR, - ${toSqlString(city)}::VARCHAR, ${toSqlString( - state - )}::VARCHAR, ${toSqlString(zip)}::VARCHAR, - ${toSqlString(phone)}::VARCHAR, - ${toSqlNumeric(latitude)}::NUMERIC, ${toSqlNumeric(longitude)}::NUMERIC, - ${toSqlString(website)}::VARCHAR, ${toSqlBoolean(inactive)}, - ${toSqlString(notes)}::VARCHAR, ${toSqlString(requirements)}::VARCHAR, - ${toSqlString(adminNotes)}::VARCHAR, ${toSqlNumeric(loginId)}::INT, - ${toSqlString(parentOrganization)}::VARCHAR, ${toSqlString( - physicalAccess - )}::VARCHAR, - ${toSqlString(email)}::VARCHAR, ${toSqlString(items)}::VARCHAR, - ${toSqlString(services)}::VARCHAR, ${toSqlString(facebook)}::VARCHAR, - ${toSqlString(twitter)}::VARCHAR, ${toSqlString(pinterest)}::VARCHAR, - ${toSqlString(linkedin)}::VARCHAR, ${toSqlString(description)}::VARCHAR, - ${toSqlTimestamp(submittedDate)}::TIMESTAMPTZ, ${toSqlNumeric( - submittedLoginId - )}::INT, - ${toSqlTimestamp(approvedDate)}::TIMESTAMP, - ${toSqlNumeric(reviewedLoginId)}::INT, - ${toSqlTimestamp(assignedDate)}::TIMESTAMP, ${toSqlNumeric( - assignedLoginId - )}::INT, - ${toSqlTimestamp(claimedDate)}::TIMESTAMP, ${toSqlNumeric( - claimedLoginId - )}::INT, - ${toSqlString(reviewNotes)}::VARCHAR, ${toSqlString(instagram)}::VARCHAR, - ${toSqlString(adminContactName)}::VARCHAR, ${toSqlString( - adminContactPhone - )}::VARCHAR, - ${toSqlString(adminContactEmail)}::VARCHAR, - ${toSqlString(donationContactName)}::VARCHAR, - ${toSqlString(donationContactPhone)}::VARCHAR, - ${toSqlString(donationContactEmail)}::VARCHAR, - ${toSqlBoolean(donationPickup)}, - ${toSqlBoolean(donationAcceptFrozen)}, - ${toSqlBoolean(donationAcceptRefrigerated)}, - ${toSqlBoolean(donationAcceptPerishable)}, - ${toSqlString(donationSchedule)}::VARCHAR, - ${toSqlString(donationDeliveryInstructions)}::VARCHAR, - ${toSqlString(donationNotes)}::VARCHAR, - ${toSqlString(covidNotes)}::VARCHAR, - ${toSqlString(categoryNotes)}::VARCHAR, - ${toSqlString(eligibilityNotes)}::VARCHAR, - ${toSqlString(foodTypes)}::VARCHAR, - ${toSqlString(languages)}::VARCHAR, - ${toSqlBoolean(confirmedName)}, - ${toSqlBoolean(confirmedCategories)}, - ${toSqlBoolean(confirmedAddress)}, - ${toSqlBoolean(confirmedPhone)}, - ${toSqlBoolean(confirmedEmail)}, - ${toSqlBoolean(confirmedHours)}, - ${toSqlNumeric(verificationStatusId)}::INT, - ${toSqlBoolean(inactiveTemporary)}, - ${categories}, ${formattedHours})`; + ${toSqlNumeric(0)}::INT, + ${toSqlNumeric(tenantId)}::INT, + ${toSqlString(name)}::VARCHAR, + ${toSqlString(address1)}::VARCHAR, + ${toSqlString(address2)}::VARCHAR, + ${toSqlString(city)}::VARCHAR, + ${toSqlString(state)}::VARCHAR, + ${toSqlString(zip)}::VARCHAR, + ${toSqlString(phone)}::VARCHAR, + ${toSqlNumeric(latitude)}::NUMERIC, + ${toSqlNumeric(longitude)}::NUMERIC, + ${toSqlString(website)}::VARCHAR, ${toSqlBoolean(inactive)}, + ${toSqlString(notes)}::VARCHAR, + ${toSqlString(requirements)}::VARCHAR, + ${toSqlString(adminNotes)}::VARCHAR, + ${toSqlNumeric(loginId)}::INT, + ${toSqlString(parentOrganization)}::VARCHAR, + ${toSqlString(physicalAccess)}::VARCHAR, + ${toSqlString(email)}::VARCHAR, + ${toSqlString(items)}::VARCHAR, + ${toSqlString(services)}::VARCHAR, ${toSqlString(facebook)}::VARCHAR, + ${toSqlString(twitter)}::VARCHAR, ${toSqlString(pinterest)}::VARCHAR, + ${toSqlString(linkedin)}::VARCHAR, ${toSqlString(description)}::VARCHAR, + ${toSqlTimestamp(submittedDate)}::TIMESTAMPTZ, + ${toSqlNumeric(submittedLoginId)}::INT, + ${toSqlTimestamp(approvedDate)}::TIMESTAMP, + ${toSqlNumeric(reviewedLoginId)}::INT, + ${toSqlTimestamp(assignedDate)}::TIMESTAMP, + ${toSqlNumeric(assignedLoginId)}::INT, + ${toSqlTimestamp(claimedDate)}::TIMESTAMP, + ${toSqlNumeric(claimedLoginId)}::INT, + ${toSqlString(reviewNotes)}::VARCHAR, + ${toSqlString(instagram)}::VARCHAR, + ${toSqlString(adminContactName)}::VARCHAR, + ${toSqlString(adminContactPhone)}::VARCHAR, + ${toSqlString(adminContactEmail)}::VARCHAR, + ${toSqlString(donationContactName)}::VARCHAR, + ${toSqlString(donationContactPhone)}::VARCHAR, + ${toSqlString(donationContactEmail)}::VARCHAR, + ${toSqlBoolean(donationPickup)}, + ${toSqlBoolean(donationAcceptFrozen)}, + ${toSqlBoolean(donationAcceptRefrigerated)}, + ${toSqlBoolean(donationAcceptPerishable)}, + ${toSqlString(donationSchedule)}::VARCHAR, + ${toSqlString(donationDeliveryInstructions)}::VARCHAR, + ${toSqlString(donationNotes)}::VARCHAR, + ${toSqlString(covidNotes)}::VARCHAR, + ${toSqlString(categoryNotes)}::VARCHAR, + ${toSqlString(eligibilityNotes)}::VARCHAR, + ${toSqlString(foodTypes)}::VARCHAR, + ${toSqlString(languages)}::VARCHAR, + ${toSqlBoolean(confirmedName)}, + ${toSqlBoolean(confirmedCategories)}, + ${toSqlBoolean(confirmedAddress)}, + ${toSqlBoolean(confirmedPhone)}, + ${toSqlBoolean(confirmedEmail)}, + ${toSqlBoolean(confirmedHours)}, + ${toSqlBoolean(confirmedFoodTypes)}, + ${toSqlNumeric(verificationStatusId)}::INT, + ${toSqlBoolean(inactiveTemporary)}, + ${categories}, ${formattedHours}, + ${toSqlBoolean(foodBakery)}, ${toSqlBoolean(foodDryGoods)}, + ${toSqlBoolean(foodProduce)}, ${toSqlBoolean(foodDairy)}, + ${toSqlBoolean(foodPrepared)}, ${toSqlBoolean(foodMeat)} + )`; const stakeholderResult = await pool.query(invokeSprocSql); const id = stakeholderResult.rows[0].s_id; return { id }; @@ -787,8 +817,15 @@ const update = async (model) => { confirmedPhone, confirmedEmail, confirmedHours, + confirmedFoodTypes, verificationStatusId, inactiveTemporary, + foodBakery, + foodDryGoods, + foodProduce, + foodDairy, + foodPrepared, + foodMeat, } = model; let hoursSqlValues = hours.length @@ -812,70 +849,74 @@ const update = async (model) => { // (ARRAY['(2,Wed,13:02,13:04)', '(3,Thu,07:00,08:00)'])::stakeholder_hours[]); --array of stakeholder_hours // objects, which are defined as a postgres type (see repo file for more detail on this type). const invokeSprocSql = `CALL update_stakeholder ( - ${toSqlString(name)}::VARCHAR, ${toSqlString( - address1 - )}::VARCHAR, ${toSqlString(address2)}::VARCHAR, - ${toSqlString(city)}::VARCHAR, ${toSqlString( - state - )}::VARCHAR, ${toSqlString(zip)}::VARCHAR, ${toSqlString(phone)}::VARCHAR, - ${toSqlNumeric(latitude)}::NUMERIC, ${toSqlNumeric( - longitude - )}::NUMERIC, ${toSqlString(website)}::VARCHAR, - ${toSqlBoolean(inactive)}, ${toSqlString(notes)}::VARCHAR, ${toSqlString( - requirements - )}::VARCHAR, - ${toSqlString(adminNotes)}::VARCHAR, ${toSqlString( - parentOrganization - )}::VARCHAR, ${toSqlString(physicalAccess)}::VARCHAR, - ${toSqlString(email)}::VARCHAR, ${toSqlString( - items - )}::VARCHAR, ${toSqlString(services)}::VARCHAR, ${toSqlString( - facebook - )}::VARCHAR, - ${toSqlString(twitter)}::VARCHAR, ${toSqlString( - pinterest - )}::VARCHAR, ${toSqlString(linkedin)}::VARCHAR, ${toSqlString( - description - )}::VARCHAR, - ${toSqlNumeric(loginId)}::INT, ${toSqlTimestamp( - submittedDate - )}::TIMESTAMPTZ, ${toSqlNumeric(submittedLoginId)}::INT, - ${toSqlTimestamp(approvedDate)}::TIMESTAMP, - ${toSqlNumeric(reviewedLoginId)}::INT, - ${toSqlTimestamp(assignedDate)}::TIMESTAMP, ${toSqlNumeric( - assignedLoginId - )}::INT, ${toSqlTimestamp(claimedDate)}::TIMESTAMP, - ${toSqlNumeric(claimedLoginId)}::INT, ${toSqlString( - reviewNotes - )}::VARCHAR, ${toSqlString(instagram)}::VARCHAR, - ${toSqlString(adminContactName)}::VARCHAR, ${toSqlString( - adminContactPhone - )}::VARCHAR, ${toSqlString(adminContactEmail)}::VARCHAR, - ${toSqlString(donationContactName)}::VARCHAR, ${toSqlString( - donationContactPhone - )}::VARCHAR, ${toSqlString(donationContactEmail)}::VARCHAR, - ${toSqlBoolean(donationPickup)}, ${toSqlBoolean( - donationAcceptFrozen - )}, ${toSqlBoolean(donationAcceptRefrigerated)}, - ${toSqlBoolean(donationAcceptPerishable)}, ${toSqlString( - donationSchedule - )}::VARCHAR, ${toSqlString(donationDeliveryInstructions)}::VARCHAR, - ${toSqlString(donationNotes)}::VARCHAR, ${toSqlString( - covidNotes - )}::VARCHAR, ${toSqlString(categoryNotes)}::VARCHAR, - ${toSqlString(eligibilityNotes)}::VARCHAR, ${toSqlString( - foodTypes - )}::VARCHAR, ${toSqlString(languages)}::VARCHAR, - ${toSqlBoolean(confirmedName)}, ${toSqlBoolean( - confirmedCategories - )}, ${toSqlBoolean(confirmedAddress)}, - ${toSqlBoolean(confirmedPhone)}, ${toSqlBoolean( - confirmedEmail - )}, ${toSqlBoolean(confirmedHours)}, - ${toSqlNumeric(verificationStatusId)}::INT, ${toSqlBoolean( - inactiveTemporary - )}, - ${id}, ${categories}, ${formattedHours})`; + ${toSqlString(name)}::VARCHAR, + ${toSqlString(address1)}::VARCHAR, + ${toSqlString(address2)}::VARCHAR, + ${toSqlString(city)}::VARCHAR, + ${toSqlString(state)}::VARCHAR, + ${toSqlString(zip)}::VARCHAR, + ${toSqlString(phone)}::VARCHAR, + ${toSqlNumeric(latitude)}::NUMERIC, + ${toSqlNumeric(longitude)}::NUMERIC, + ${toSqlString(website)}::VARCHAR, + ${toSqlBoolean(inactive)}, + ${toSqlString(notes)}::VARCHAR, + ${toSqlString(requirements)}::VARCHAR, + ${toSqlString(adminNotes)}::VARCHAR, + ${toSqlString(parentOrganization)}::VARCHAR, + ${toSqlString(physicalAccess)}::VARCHAR, + ${toSqlString(email)}::VARCHAR, + ${toSqlString(items)}::VARCHAR, + ${toSqlString(services)}::VARCHAR, + ${toSqlString(facebook)}::VARCHAR, + ${toSqlString(twitter)}::VARCHAR, + ${toSqlString(pinterest)}::VARCHAR, + ${toSqlString(linkedin)}::VARCHAR, + ${toSqlString(description)}::VARCHAR, + ${toSqlNumeric(loginId)}::INT, + ${toSqlTimestamp(submittedDate)}::TIMESTAMPTZ, + ${toSqlNumeric(submittedLoginId)}::INT, + ${toSqlTimestamp(approvedDate)}::TIMESTAMP, + ${toSqlNumeric(reviewedLoginId)}::INT, + ${toSqlTimestamp(assignedDate)}::TIMESTAMP, + ${toSqlNumeric(assignedLoginId)}::INT, + ${toSqlTimestamp(claimedDate)}::TIMESTAMP, + ${toSqlNumeric(claimedLoginId)}::INT, + ${toSqlString(reviewNotes)}::VARCHAR, + ${toSqlString(instagram)}::VARCHAR, + ${toSqlString(adminContactName)}::VARCHAR, + ${toSqlString(adminContactPhone)}::VARCHAR, + ${toSqlString(adminContactEmail)}::VARCHAR, + ${toSqlString(donationContactName)}::VARCHAR, + ${toSqlString(donationContactPhone)}::VARCHAR, + ${toSqlString(donationContactEmail)}::VARCHAR, + ${toSqlBoolean(donationPickup)}, + ${toSqlBoolean(donationAcceptFrozen)}, + ${toSqlBoolean(donationAcceptRefrigerated)}, + ${toSqlBoolean(donationAcceptPerishable)}, + ${toSqlString(donationSchedule)}::VARCHAR, + ${toSqlString(donationDeliveryInstructions)}::VARCHAR, + ${toSqlString(donationNotes)}::VARCHAR, + ${toSqlString(covidNotes)}::VARCHAR, + ${toSqlString(categoryNotes)}::VARCHAR, + ${toSqlString(eligibilityNotes)}::VARCHAR, + ${toSqlString(foodTypes)}::VARCHAR, + ${toSqlString(languages)}::VARCHAR, + ${toSqlBoolean(confirmedName)}, + ${toSqlBoolean(confirmedCategories)}, + ${toSqlBoolean(confirmedAddress)}, + ${toSqlBoolean(confirmedPhone)}, + ${toSqlBoolean(confirmedEmail)}, ${toSqlBoolean(confirmedHours)}, + ${toSqlBoolean(confirmedFoodTypes)}, + ${toSqlNumeric(verificationStatusId)}::INT, + ${toSqlBoolean(inactiveTemporary)}, + ${id}, + ${categories}, + ${formattedHours}, + ${toSqlBoolean(foodBakery)}, ${toSqlBoolean(foodDryGoods)}, + ${toSqlBoolean(foodProduce)}, ${toSqlBoolean(foodDairy)}, + ${toSqlBoolean(foodPrepared)}, ${toSqlBoolean(foodMeat)} + )`; try { await pool.query(invokeSprocSql); diff --git a/client/package-lock.json b/client/package-lock.json index be803c288..9a7412c48 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "food-oasis-client", - "version": "1.0.21", + "version": "1.0.28", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/client/package.json b/client/package.json index 8adb39302..c31ef3d66 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "food-oasis-client", "description": "React Client for Food Oasis", - "version": "1.0.26", + "version": "1.0.28", "author": "Hack for LA", "license": "GPL-2.0", "private": true, diff --git a/client/public/index.html b/client/public/index.html index c4d63487c..d99e01ab4 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -67,5 +67,32 @@ To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> + + + + + + + + + + + diff --git a/client/src/App.js b/client/src/App.js index 7b0e7543d..1fee4acc9 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -4,7 +4,7 @@ import { makeStyles, ThemeProvider } from "@material-ui/core/styles"; import { Grid } from "@material-ui/core"; import theme from "theme/materialUI"; import { logout } from "services/account-service"; -import { tenantId, originCoordinates } from "helpers/Configuration"; +import { tenantId, defaultCoordinates } from "helpers/Configuration"; // Components import { UserContext } from "components/user-context"; @@ -75,8 +75,8 @@ function App() { const [toast, setToast] = useState({ message: "" }); const [bgImg, setBgImg] = useState(""); const [origin, setOrigin] = useState({ - latitude: originCoordinates.lat, - longitude: originCoordinates.lon, + latitude: defaultCoordinates.lat, + longitude: defaultCoordinates.lon, }); useEffect(() => { @@ -127,8 +127,8 @@ function App() { (error) => { console.log(`Getting browser location failed: ${error.message}`); const userCoordinates = { - latitude: originCoordinates.lat, - longitude: originCoordinates.lon, + latitude: defaultCoordinates.lat, + longitude: defaultCoordinates.lon, }; setUserCoordinates(userCoordinates); } @@ -176,7 +176,8 @@ function App() { diff --git a/client/src/components/Results/ResultsContainer.js b/client/src/components/Results/ResultsContainer.js index 4836b6017..5b019d7ad 100644 --- a/client/src/components/Results/ResultsContainer.js +++ b/client/src/components/Results/ResultsContainer.js @@ -1,11 +1,12 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { makeStyles } from "@material-ui/core/styles"; import { Grid } from "@material-ui/core"; import { useOrganizationBests } from "hooks/useOrganizationBests"; import useCategoryIds from "hooks/useCategoryIds"; import { isMobile } from "helpers"; -import { originCoordinates } from "../../helpers/Configuration"; +import { defaultCoordinates } from "helpers/Configuration"; +import { DEFAULT_CATEGORIES } from "constants/stakeholder"; import Filters from "./ResultsFilters"; import List from "./ResultsList"; @@ -22,88 +23,49 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function ResultsContainer(props) { +export default function ResultsContainer({ + userCoordinates, + origin, + setOrigin, + setToast, +}) { // Component state - const storage = window.sessionStorage; - const { userCoordinates, userSearch, setToast } = props; const { data, search } = useOrganizationBests(); const [sortedData, setSortedData] = useState([]); const classes = useStyles(); - const windowSize = window.innerWidth > 960 ? true : false; - const [isWindowWide, changeWindow] = useState(windowSize); - const [selectedStakeholder, onSelectStakeholder] = useState(null); const [isMapView, setIsMapView] = useState(true); + const mobileView = isMobile(); - const doSelectStakeholder = (stakeholder) => { - if (stakeholder && !isMobile) { - setViewport({ - ...viewport, - latitude: stakeholder.latitude, - longitude: stakeholder.longitude, - }); - } - onSelectStakeholder(stakeholder); - }; + const { categoryIds, toggleCategory } = useCategoryIds([]); + const [isVerifiedSelected, selectVerified] = useState(false); + const [viewport, setViewport] = useState({ + zoom: defaultCoordinates.zoom, + latitude: origin.latitude, + longitude: origin.longitude, + logoPosition: "top-left", + }); + + const doSelectStakeholder = useCallback( + (stakeholder) => { + if (stakeholder && !isMobile) { + setViewport({ + ...viewport, + latitude: stakeholder.latitude, + longitude: stakeholder.longitude, + }); + } + onSelectStakeholder(stakeholder); + }, + [viewport, setViewport] + ); const switchResultsView = () => { doSelectStakeholder(); setIsMapView(!isMapView); }; - const initialCategories = storage.categoryIds - ? JSON.parse(storage.categoryIds) - : []; - const { categoryIds, toggleCategory } = useCategoryIds(initialCategories); - - const initialCoords = { - locationName: userSearch - ? userSearch.locationName - : storage.origin - ? JSON.parse(storage.origin).locationName - : "", - latitude: userSearch - ? userSearch.latitude - : storage.origin - ? JSON.parse(storage.origin).latitude - : userCoordinates - ? userCoordinates.latitude - : originCoordinates.lat, - longitude: userSearch - ? userSearch.longitude - : storage.origin - ? JSON.parse(storage.origin).longitude - : userCoordinates - ? userCoordinates.longitude - : originCoordinates.lon, - }; - - const [radius, setRadius] = useState( - storage?.radius ? JSON.parse(storage.radius) : 5 - ); - const [origin, setOrigin] = useState(initialCoords); - const [isVerifiedSelected, selectVerified] = useState( - storage?.verified ? JSON.parse(storage.verified) : false - ); - - const viewPortHash = { - 1: 13.5, - 2: 12.5, - 3: 12, - 5: 11, - 10: 10, - 20: 9, - 50: 8, - }; - - const [viewport, setViewport] = useState({ - zoom: viewPortHash[radius], - latitude: origin.latitude || JSON.parse(storage.origin).latitude, - longitude: origin.longitude || JSON.parse(storage.origin).longitude, - logoPosition: "top-left", - }); - // Component effects useEffect(() => { @@ -130,28 +92,46 @@ export default function ResultsContainer(props) { setSortedData(data.sort(sortOrganizations)); }, [data]); - useEffect(() => { - const changeInputContainerWidth = () => { - window.innerWidth > 960 ? changeWindow(true) : changeWindow(false); - }; - - window.addEventListener("resize", changeInputContainerWidth); - - return () => - window.removeEventListener("resize", changeInputContainerWidth); - }, []); + const handleSearch = useCallback( + (e, center, bounds) => { + if (e) e.preventDefault(); + search({ + latitude: + (center && center.lat) || origin.latitude || userCoordinates.latitude, + longitude: + (center && center.lng) || + origin.longitude || + userCoordinates.longitude, + categoryIds: categoryIds.length ? categoryIds : DEFAULT_CATEGORIES, + isInactive: "either", + verificationStatusId: 0, + bounds, + radius: defaultCoordinates.radius, + }); - useEffect(() => { - return () => { - sessionStorage.clear(); - }; - }, []); + if (!center) + setViewport({ + zoom: defaultCoordinates.zoom, + latitude: origin.latitude, + longitude: origin.longitude, + }); + doSelectStakeholder(null); + }, + [ + search, + origin.latitude, + origin.longitude, + userCoordinates.latitude, + userCoordinates.longitude, + categoryIds, + setViewport, + doSelectStakeholder, + ] + ); return ( <> - {(!isMobile || (isMobile && !isMapView)) && ( + {(!mobileView || (mobileView && !isMapView)) && ( )} - {(!isMobile || (isMobile && isMapView)) && ( + {(!mobileView || (mobileView && isMapView)) && ( )} diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index ba921373a..70bf27c93 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -1,13 +1,13 @@ import React, { useCallback, useEffect } from "react"; import PropTypes from "prop-types"; import { makeStyles } from "@material-ui/core/styles"; -import { Grid, Select, MenuItem, Button, Box } from "@material-ui/core"; +import { Grid, Button, Box } from "@material-ui/core"; import SearchIcon from "@material-ui/icons/Search"; +import LocationSearchingIcon from "@material-ui/icons/LocationSearching"; import { MEAL_PROGRAM_CATEGORY_ID, FOOD_PANTRY_CATEGORY_ID, - DEFAULT_CATEGORIES, } from "constants/stakeholder"; import { isMobile } from "helpers"; @@ -27,23 +27,57 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.primary.main, padding: "1rem 0", flex: "1 0 auto", + position: "sticky", + top: "48px", + zIndex: 1, + justifyContent: "center", }, inputContainer: { display: "flex", alignItems: "center", + width: "100%", + maxWidth: "30rem", + margin: "0 0.5rem", + }, + form: { + all: "inherit", + backgroundColor: "white", + borderRadius: "6px", }, searchIcon: { width: 32, height: 32, }, - submit: { + nearbyIcon: { + maxWidth: "30px", + }, + nearbySearch: { height: "40px", minWidth: "25px", + padding: 0, + marginLeft: "5px", + borderRadius: 0, + backgroundColor: "white", + boxShadow: "none", + "& .MuiButton-startIcon": { + margin: 0, + }, + "&.Mui-disabled": { + opacity: 0.8, + backgroundColor: "white", + }, + "&:hover": { + boxShadow: "none", + }, + }, + submit: { + height: "40px", backgroundColor: "#BCE76D", borderRadius: "0 6px 6px 0", boxShadow: "none", "& .MuiButton-startIcon": { marginRight: 0, + marginLeft: "3px", }, "&.Mui-disabled": { backgroundColor: "#BCE76D", @@ -53,35 +87,23 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: "#C7F573", boxShadow: "none", }, - [theme.breakpoints.down("xs")]: { - marginRight: ".5rem", - }, }, buttonHolder: { display: "flex", - [theme.breakpoints.down("xs")]: { + [theme.breakpoints.down("sm")]: { marginTop: "0.5rem", }, }, })); -const distanceInfo = [0, 1, 2, 3, 5, 10, 20, 50, 100, 500]; - const ResultsFilters = ({ - search, - isWindowWide, - viewport, - setViewport, - doSelectStakeholder, + handleSearch, origin, setOrigin, - radius, - setRadius, isVerifiedSelected, userCoordinates, categoryIds, toggleCategory, - viewPortHash, isMapView, switchResultsView, }) => { @@ -89,57 +111,6 @@ const ResultsFilters = ({ const isMealsSelected = categoryIds.indexOf(MEAL_PROGRAM_CATEGORY_ID) >= 0; const isPantrySelected = categoryIds.indexOf(FOOD_PANTRY_CATEGORY_ID) >= 0; - const doHandleSearch = useCallback( - (e) => { - if (e) e.preventDefault(); - const storage = window.sessionStorage; - search({ - latitude: - origin.latitude || - userCoordinates.latitude || - JSON.parse(storage.origin).latitude, - longitude: - origin.longitude || - userCoordinates.longitude || - JSON.parse(storage.origin).longitude, - radius, - categoryIds: categoryIds.length ? categoryIds : DEFAULT_CATEGORIES, - isInactive: "either", - verificationStatusId: 0, - }); - if (origin.locationName && origin.latitude && origin.longitude) - storage.origin = JSON.stringify({ - locationName: origin.locationName, - latitude: origin.latitude, - longitude: origin.longitude, - }); - - storage.categoryIds = JSON.stringify(categoryIds); - storage.radius = JSON.stringify(radius); - storage.verified = JSON.stringify(isVerifiedSelected); - setViewport({ - zoom: viewPortHash[radius], - latitude: origin.latitude, - longitude: origin.longitude, - }); - doSelectStakeholder(null); - }, - [ - search, - origin.locationName, - origin.latitude, - origin.longitude, - userCoordinates.latitude, - userCoordinates.longitude, - radius, - categoryIds, - isVerifiedSelected, - setViewport, - doSelectStakeholder, - viewPortHash, - ] - ); - const toggleMeal = useCallback(() => { toggleCategory(MEAL_PROGRAM_CATEGORY_ID); }, [toggleCategory]); @@ -149,28 +120,14 @@ const ResultsFilters = ({ }, [toggleCategory]); useEffect(() => { - doHandleSearch(); + handleSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [origin, radius, categoryIds, isVerifiedSelected, toggleCategory]); + }, [origin, categoryIds, isVerifiedSelected, toggleCategory]); - const handleDistanceChange = (distance) => { - setRadius(distance); - setViewport({ - ...viewport, - zoom: viewPortHash[distance], - }); - }; + const mobileView = isMobile(); return ( - + - - - + )} {stakeholders && stakeholders .filter( @@ -106,9 +163,9 @@ function Map({ !(sh.inactive || sh.inactiveTemporary) ) .map((stakeholder) => { - const categories = stakeholder.categories.filter(({ id }) => { - return categoryIdsOrDefault.includes(id); - }); + const categories = stakeholder.categories.filter(({ id }) => + categoryIdsOrDefault.includes(id) + ); return ( - {!!selectedStakeholder && isMobile && ( + {!!selectedStakeholder && mobileView && ( ({ }, container: { width: "100%", - [theme.breakpoints.down("xs")]: { + [theme.breakpoints.down("sm")]: { marginLeft: ".5rem", }, position: "relative", @@ -77,8 +77,6 @@ export default function Search({ userCoordinates, setOrigin, origin }) { }); const handleInputChange = (event) => { - console.log(event); - // if (!event.target.value) return; setSelectedPlace(event.target.value); updateNewInputValue(event.target.value); fetchMapboxResults(event.target.value); diff --git a/client/src/components/Stakeholder/StakeholderDetails.js b/client/src/components/Stakeholder/StakeholderDetails.js index db8e4f7a7..428edf6be 100644 --- a/client/src/components/Stakeholder/StakeholderDetails.js +++ b/client/src/components/Stakeholder/StakeholderDetails.js @@ -21,10 +21,14 @@ const useStyles = makeStyles((theme, props) => ({ width: "100%", display: "flex", flexDirection: "column", - justifyContent: "space-between", + textAlign: "center", padding: "1em", alignItems: "center", paddingBottom: "5em", + flexShrink: 0, + [theme.breakpoints.down("xs")]: { + fontSize: "12px", + }, }, topInfo: { width: "100%", diff --git a/client/src/components/Stakeholder/StakeholderPreview.js b/client/src/components/Stakeholder/StakeholderPreview.js index 4f83763d2..44befeab2 100644 --- a/client/src/components/Stakeholder/StakeholderPreview.js +++ b/client/src/components/Stakeholder/StakeholderPreview.js @@ -154,12 +154,14 @@ const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { >
-
- {stakeholder.distance >= 10 - ? stakeholder.distance.toString().substring(0, 3).padEnd(4, "0") - : stakeholder.distance.toString().substring(0, 3)}{" "} - mi -
+ {stakeholder.distance ? ( +
+ {stakeholder.distance >= 10 + ? stakeholder.distance.toString().substring(0, 3).padEnd(4, "0") + : stakeholder.distance.toString().substring(0, 3)}{" "} + mi +
+ ) : null}

{stakeholder.name}

@@ -232,7 +234,11 @@ const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { Call )} -
diff --git a/client/src/components/StaticPagesHI/About.js b/client/src/components/StaticPagesHI/About.js index f29bf7e0d..f9698cc3a 100644 --- a/client/src/components/StaticPagesHI/About.js +++ b/client/src/components/StaticPagesHI/About.js @@ -3,10 +3,7 @@ import React from "react"; import aboutbg from "./assets/about-bg.png"; import iconSpacer from "./assets/icon-spacer.svg"; import iconSpacerBlue from "./assets/icon-spacer-blue.svg"; -import foodCycle from "./assets/food-cycle.png"; -import foodForward from "./assets/food-forward.png"; -import farmPeople from "./assets/farm-people.png"; -import foodBank from "./assets/food-bank.png"; +import foodBank from "./assets/aloha-harvest.png"; import { makeStyles } from "@material-ui/core"; import Footer from "../Footer"; @@ -130,10 +127,13 @@ const useStyles = makeStyles(() => ({ partnersLogo: { maxWidth: "100%", height: "100%", + backgroundColor: "blue", marginBottom: "40px", "@media only screen and (min-width: 64em) ": { marginBottom: 0, }, + marginLeft: "auto", + marginRight: "auto", }, })); const About = () => { @@ -253,22 +253,7 @@ const About = () => { />

Our Partners

Food Cycle LA - Food Forward - Farm People - Food Bank diff --git a/client/src/components/StaticPagesHI/assets/aloha-harvest.png b/client/src/components/StaticPagesHI/assets/aloha-harvest.png new file mode 100644 index 000000000..f7d5eb6f6 Binary files /dev/null and b/client/src/components/StaticPagesHI/assets/aloha-harvest.png differ diff --git a/client/src/components/StaticPagesHI/assets/farm-people.png b/client/src/components/StaticPagesHI/assets/farm-people.png deleted file mode 100644 index efae672c4..000000000 Binary files a/client/src/components/StaticPagesHI/assets/farm-people.png and /dev/null differ diff --git a/client/src/components/StaticPagesHI/assets/fola-logo-footer.png b/client/src/components/StaticPagesHI/assets/fola-logo-footer.png deleted file mode 100644 index 35fea1736..000000000 Binary files a/client/src/components/StaticPagesHI/assets/fola-logo-footer.png and /dev/null differ diff --git a/client/src/components/StaticPagesHI/assets/fola-logo.png b/client/src/components/StaticPagesHI/assets/fola-logo.png deleted file mode 100644 index 189bb06ef..000000000 Binary files a/client/src/components/StaticPagesHI/assets/fola-logo.png and /dev/null differ diff --git a/client/src/components/StaticPagesHI/assets/food-bank.png b/client/src/components/StaticPagesHI/assets/food-bank.png deleted file mode 100644 index 63bee55c7..000000000 Binary files a/client/src/components/StaticPagesHI/assets/food-bank.png and /dev/null differ diff --git a/client/src/components/StaticPagesHI/assets/food-cycle.png b/client/src/components/StaticPagesHI/assets/food-cycle.png deleted file mode 100644 index c6545cf27..000000000 Binary files a/client/src/components/StaticPagesHI/assets/food-cycle.png and /dev/null differ diff --git a/client/src/components/StaticPagesHI/assets/food-forward.png b/client/src/components/StaticPagesHI/assets/food-forward.png deleted file mode 100644 index 1f1023a77..000000000 Binary files a/client/src/components/StaticPagesHI/assets/food-forward.png and /dev/null differ diff --git a/client/src/components/Verification/OrganizationEdit.js b/client/src/components/Verification/OrganizationEdit.js index d255fbe59..5ad1786f9 100644 --- a/client/src/components/Verification/OrganizationEdit.js +++ b/client/src/components/Verification/OrganizationEdit.js @@ -190,8 +190,15 @@ const emptyOrganization = { confirmedEmail: false, confirmedPhone: false, confirmedHours: false, + confirmedFoodTypes: false, verificationStatusId: VERIFICATION_STATUS.NEEDS_VERIFICATION, inactiveTemporary: false, + foodBakery: false, + foodDryGoods: false, + foodProduce: false, + foodDairy: false, + foodPrepared: false, + foodMeat: false, }; const OrganizationEdit = (props) => { @@ -1091,113 +1098,244 @@ const OrganizationEdit = (props) => { Details for Food Seekers to See - - + + + Food Types + + + + setFieldValue( + "confirmedFoodTypes", + e.target.checked + ) + } + onBlur={handleBlur} + size="medium" + /> + } + label="Confirm Food Types" + /> + - - + + setFieldValue("foodBakery", !values.foodBakery) + } + onBlur={handleBlur} + /> + } + label="Baked Goods" /> - - + + setFieldValue("foodDryGoods", !values.foodDryGoods) + } + onBlur={handleBlur} + /> + } + label="Dry Goods" /> - - + + setFieldValue("foodProduce", !values.foodProduce) + } + onBlur={handleBlur} + /> } + label="Produce" /> - - + + setFieldValue("foodDairy", !values.foodDairy) + } + onBlur={handleBlur} + /> } + label="Dairy" /> - - + + setFieldValue("foodPrepared", !values.foodPrepared) + } + onBlur={handleBlur} + /> + } + label="Prepared Food" /> - - + + setFieldValue("foodMeat", !values.foodMeat) + } + onBlur={handleBlur} + /> + } + label="Meat" /> + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/Verification/SearchCriteria.js b/client/src/components/Verification/SearchCriteria.js index 92a0e945a..6522bbcfc 100644 --- a/client/src/components/Verification/SearchCriteria.js +++ b/client/src/components/Verification/SearchCriteria.js @@ -20,7 +20,7 @@ import { import RadioTrueFalseEither from "../RadioTrueFalseEither"; import LocationAutocomplete from "../LocationAutocomplete"; import AccountAutocomplete from "../AccountAutocomplete"; -import { originCoordinates } from "../../helpers/Configuration"; +import { defaultCoordinates } from "../../helpers/Configuration"; const useStyles = makeStyles((theme) => ({ card: { @@ -58,8 +58,10 @@ const SearchCriteria = ({ : "custom" ); - const [customLatitude, setCustomLatitude] = useState(originCoordinates.lat); - const [customLongitude, setCustomLongitude] = useState(originCoordinates.lon); + const [customLatitude, setCustomLatitude] = useState(defaultCoordinates.lat); + const [customLongitude, setCustomLongitude] = useState( + defaultCoordinates.lon + ); const [customPlaceName, setCustomPlaceName] = useState(""); useEffect(() => { diff --git a/client/src/components/Verification/VerificationAdminGrid.js b/client/src/components/Verification/VerificationAdminGrid.js index 7f7f34c04..376a1b890 100644 --- a/client/src/components/Verification/VerificationAdminGrid.js +++ b/client/src/components/Verification/VerificationAdminGrid.js @@ -159,6 +159,12 @@ const adminColumns = [ formatter: confirmationFormatter, width: 60, }, + { + key: "confirmedFoodTypes", + name: "FoodTypes", + formatter: confirmationFormatter, + width: 60, + }, { key: "neighborhoodName", name: "Neighborhood", diff --git a/client/src/helpers/Configuration.js b/client/src/helpers/Configuration.js index ff1aa93b6..07e2b3d05 100644 --- a/client/src/helpers/Configuration.js +++ b/client/src/helpers/Configuration.js @@ -9,13 +9,13 @@ export const tenantId = (() => ? 2 : 1)(); -export const originCoordinates = (() => { +export const defaultCoordinates = (() => { switch (tenantId) { case 3: - return { lat: 21.33629, lon: -157.89435 }; + return { lat: 21.3101548, lon: -157.8428712, zoom: 12, radius: 5 }; case 2: - return { lat: 38.576711, lon: -121.493671 }; + return { lat: 38.3949164, lon: -122.7287326, zoom: 10, radius: 8 }; default: - return { lat: 34.07872, lon: -118.243328 }; + return { lat: 34.0354899, lon: -118.2439235, zoom: 12, radius: 5 }; } })(); diff --git a/client/src/helpers/index.js b/client/src/helpers/index.js index 17e665da7..c89cf3e4e 100644 --- a/client/src/helpers/index.js +++ b/client/src/helpers/index.js @@ -1,3 +1,5 @@ +import theme from "theme/materialUI"; + export const getGoogleMapsUrl = (zip, address1, address2) => { const baseUrl = `https://google.com/maps/place/`; @@ -17,9 +19,7 @@ export const getGoogleMapsUrl = (zip, address1, address2) => { return `${baseUrl}${address1url},+${zip}`; }; -export const isMobile = new RegExp("Mobi", "i").test(navigator.userAgent) - ? true - : false; +export const isMobile = () => window.innerWidth < theme.breakpoints.values.sm; export const extractNumbers = (numbers) => numbers.split(/(and)|,|&+/).map((n) => { diff --git a/client/src/hooks/useOrganizationBests.js b/client/src/hooks/useOrganizationBests.js index d4d8ed82a..0af6773a9 100644 --- a/client/src/hooks/useOrganizationBests.js +++ b/client/src/hooks/useOrganizationBests.js @@ -13,6 +13,7 @@ export const useOrganizationBests = () => { latitude, longitude, radius, + bounds, categoryIds, isInactive, verificationStatusId, @@ -27,7 +28,7 @@ export const useOrganizationBests = () => { //if (!categoryIds || categoryIds.length === 0) return; try { setState({ data: null, loading: true, error: false }); - const stakeholders = await stakeholderService.search({ + let params = { name, categoryIds, latitude, @@ -35,7 +36,18 @@ export const useOrganizationBests = () => { distance: radius, isInactive, verificationStatusId, - }); + }; + if (bounds) { + const { maxLat, maxLng, minLat, minLng } = bounds; + params = { + ...params, + maxLng, + maxLat, + minLng, + minLat, + }; + } + const stakeholders = await stakeholderService.search(params); setState({ data: stakeholders, loading: false, error: false }); return stakeholders; } catch (err) { diff --git a/client/src/services/stakeholder-best-service.js b/client/src/services/stakeholder-best-service.js index a6ad373ac..de7009d02 100644 --- a/client/src/services/stakeholder-best-service.js +++ b/client/src/services/stakeholder-best-service.js @@ -14,6 +14,10 @@ const toLocalMoment = (ts) => { categoryIds - array of integers corresponding to the desired categories latitude longitude + maxLat - optional + maxLng - optional + minLat - optional + minLng - optional distance - radius around latitude and longitude isAssigned - ("yes", "no", "either") isSubmitted - ("yes", "no", "either") diff --git a/migrations/1604419645716_implement-food-type-checkboxes-678.js b/migrations/1604419645716_implement-food-type-checkboxes-678.js new file mode 100644 index 000000000..adb33740b --- /dev/null +++ b/migrations/1604419645716_implement-food-type-checkboxes-678.js @@ -0,0 +1,760 @@ +/* eslint-disable camelcase */ + +exports.shorthands = undefined; + +exports.up = (pgm) => { + pgm.sql( + ` + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_bakery boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_dry_goods boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_produce boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_dairy boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_prepared boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS food_meat boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder ADD COLUMN IF NOT EXISTS v_food_types boolean NOT NULL DEFAULT false; + + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_bakery boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_dry_goods boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_produce boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_dairy boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_prepared boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS food_meat boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_best ADD COLUMN IF NOT EXISTS v_food_types boolean NOT NULL DEFAULT false; + + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_bakery boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_dry_goods boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_produce boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_dairy boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_prepared boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS food_meat boolean NOT NULL DEFAULT false; + ALTER TABLE stakeholder_log ADD COLUMN IF NOT EXISTS v_food_types boolean NOT NULL DEFAULT false; + ` + ); + + pgm.sql( + ` + CREATE OR REPLACE PROCEDURE public.update_stakeholder( + s_name character varying, + s_address_1 character varying, + s_address_2 character varying, + s_city character varying, + s_state character varying, + s_zip character varying, + s_phone character varying, + s_latitude numeric, + s_longitude numeric, + s_website character varying, + s_inactive boolean, + s_notes character varying, + s_requirements character varying, + s_admin_notes character varying, + s_parent_organization character varying, + s_physical_access character varying, + s_email character varying, + s_items character varying, + s_services character varying, + s_facebook character varying, + s_twitter character varying, + s_pinterest character varying, + s_linkedin character varying, + s_description character varying, + s_modified_login_id integer, + s_submitted_date timestamp with time zone, + s_submitted_login_id integer, + s_approved_date timestamp without time zone, + s_reviewed_login_id integer, + s_assigned_date timestamp without time zone, + s_assigned_login_id integer, + s_claimed_date timestamp without time zone, + s_claimed_login_id integer, + s_review_notes character varying, + s_instagram character varying, + s_admin_contact_name character varying, + s_admin_contact_phone character varying, + s_admin_contact_email character varying, + s_donation_contact_name character varying, + s_donation_contact_phone character varying, + s_donation_contact_email character varying, + s_donation_pickup boolean, + s_donation_accept_frozen boolean, + s_donation_accept_refrigerated boolean, + s_donation_accept_perishable boolean, + s_donation_schedule character varying, + s_donation_delivery_instructions character varying, + s_donation_notes character varying, + s_covid_notes character varying, + s_category_notes character varying, + s_eligibility_notes character varying, + s_food_types character varying, + s_languages character varying, + s_v_name boolean, + s_v_categories boolean, + s_v_address boolean, + s_v_phone boolean, + s_v_email boolean, + s_v_hours boolean, + s_v_food_types boolean, + s_verification_status_id integer, + s_inactive_temporary boolean, + s_id integer, + categories integer[], + hours_array stakeholder_hours[], + s_food_bakery boolean, + s_food_dry_goods boolean, + s_food_produce boolean, + s_food_dairy boolean, + s_food_prepared boolean, + s_food_meat boolean) + LANGUAGE 'plpgsql' + + AS $BODY$ + DECLARE cat INT; + DECLARE hours_element stakeholder_hours; + DECLARE critical_percent INT; + + BEGIN + + SELECT CASE WHEN (s_inactive OR s_inactive_temporary) THEN + (s_v_name::integer + s_v_categories::integer + s_v_address::integer) *100/3 + ELSE + (s_v_name::integer + s_v_categories::integer + s_v_address::integer + + s_v_email::integer + s_v_phone::integer + s_v_hours::integer + + s_v_food_types::integer) *100/7 + END INTO critical_percent; + + -- update the stakeholder table itself + UPDATE stakeholder + SET + name = s_name, + address_1 = s_address_1, + address_2 = s_address_2, + city = s_city, + state = s_state, + zip = s_zip, + phone = s_phone, + latitude = s_latitude, + longitude = s_longitude, + website = s_website, + inactive = s_inactive, + notes = s_notes, + requirements = s_requirements, + admin_notes = s_admin_notes, + parent_organization = s_parent_organization, + physical_access = s_physical_access, + email = s_email, + items = s_items, + services = s_services, + facebook = s_facebook, + twitter = s_twitter, + pinterest = s_pinterest, + linkedin = s_linkedin, + description = s_description, + modified_login_id = s_modified_login_id, + modified_date = CURRENT_TIMESTAMP, + submitted_date = s_submitted_date, + submitted_login_id = s_submitted_login_id, + approved_date = s_approved_date, + reviewed_login_id = s_reviewed_login_id, + assigned_date = s_assigned_date, + assigned_login_id = s_assigned_login_id, + claimed_date = s_claimed_date, + claimed_login_id = s_claimed_login_id, + review_notes = s_review_notes, + instagram = s_instagram, + admin_contact_name = s_admin_contact_name, + admin_contact_phone = s_admin_contact_phone, + admin_contact_email = s_admin_contact_email, + donation_contact_name = s_donation_contact_name, + donation_contact_phone = s_donation_contact_phone, + donation_contact_email = s_donation_contact_email, + donation_pickup = s_donation_pickup, + donation_accept_frozen = s_donation_accept_frozen, + donation_accept_refrigerated = s_donation_accept_refrigerated, + donation_accept_perishable = s_donation_accept_perishable, + donation_schedule = s_donation_schedule, + donation_delivery_instructions = s_donation_delivery_instructions, + donation_notes = s_donation_notes, + covid_notes = s_covid_notes, + category_notes = s_category_notes, + eligibility_notes = s_eligibility_notes, + food_types = s_food_types, + languages = s_languages, + v_name = s_v_name, + v_categories = s_v_categories, + v_address = s_v_address, + v_phone = s_v_phone, + v_email = s_v_email, + v_hours = s_v_hours, + v_food_types = s_v_food_types, + verification_status_id = s_verification_status_id, + inactive_temporary = s_inactive_temporary, + hours = hours_array, + category_ids = categories, + complete_critical_percent = critical_percent, + food_bakery = s_food_bakery, + food_dry_goods = s_food_dry_goods, + food_produce = s_food_produce, + food_dairy = s_food_dairy, + food_prepared = s_food_prepared, + food_meat = s_food_meat + WHERE + id=s_id; + + -- delete previous stakeholder category + DELETE FROM stakeholder_category WHERE stakeholder_id=s_id; + -- ...and insert new stakeholder category(s) + FOREACH cat IN ARRAY categories + LOOP + INSERT INTO stakeholder_category + (stakeholder_id, category_id) + VALUES (s_id, cat); + END LOOP; + -- delete previous schedule + DELETE FROM stakeholder_schedule WHERE stakeholder_id=s_id; + -- ...and insert new schedule(s) + FOREACH hours_element IN ARRAY hours_array + LOOP + INSERT INTO stakeholder_schedule( + stakeholder_id, day_of_week, open, close, week_of_month + ) VALUES( + s_id, + hours_element.day_of_week, + hours_element.open::time without time zone, + hours_element.close::time without time zone, + hours_element.week_of_month + ); + END LOOP; + COMMIT; + END; + $BODY$; + ` + ); + + pgm.sql(` + CREATE OR REPLACE PROCEDURE public.create_stakeholder( + INOUT s_id integer, + s_tenant_id integer, + s_name character varying, + s_address_1 character varying, + s_address_2 character varying, + s_city character varying, + s_state character varying, + s_zip character varying, + s_phone character varying, + s_latitude numeric, + s_longitude numeric, + s_website character varying, + s_inactive boolean, + s_notes character varying, + s_requirements character varying, + s_admin_notes character varying, + s_created_login_id integer, + s_parent_organization character varying, + s_physical_access character varying, + s_email character varying, + s_items character varying, + s_services character varying, + s_facebook character varying, + s_twitter character varying, + s_pinterest character varying, + s_linkedin character varying, + s_description character varying, + s_submitted_date timestamp with time zone, + s_submitted_login_id integer, + s_approved_date timestamp without time zone, + s_reviewed_login_id integer, + s_assigned_date timestamp without time zone, + s_assigned_login_id integer, + s_claimed_date timestamp without time zone, + s_claimed_login_id integer, + s_review_notes character varying, + s_instagram character varying, + s_admin_contact_name character varying, + s_admin_contact_phone character varying, + s_admin_contact_email character varying, + s_donation_contact_name character varying, + s_donation_contact_phone character varying, + s_donation_contact_email character varying, + s_donation_pickup boolean, + s_donation_accept_frozen boolean, + s_donation_accept_refrigerated boolean, + s_donation_accept_perishable boolean, + s_donation_schedule character varying, + s_donation_delivery_instructions character varying, + s_donation_notes character varying, + s_covid_notes character varying, + s_category_notes character varying, + s_eligibility_notes character varying, + s_food_types character varying, + s_languages character varying, + s_v_name boolean, + s_v_categories boolean, + s_v_address boolean, + s_v_phone boolean, + s_v_email boolean, + s_v_hours boolean, + s_v_food_types boolean, + s_verification_status_id integer, + s_inactive_temporary boolean, + categories integer[], + hours_array stakeholder_hours[], + s_food_bakery boolean, + s_food_dry_goods boolean, + s_food_produce boolean, + s_food_dairy boolean, + s_food_prepared boolean, + s_food_meat boolean) + LANGUAGE 'plpgsql' + + AS $BODY$ + DECLARE cat INT; + DECLARE hours_element stakeholder_hours; + DECLARE critical_percent INT; + BEGIN + SELECT CASE WHEN (s_inactive OR s_inactive_temporary) THEN + (s_v_name::integer + s_v_categories::integer + s_v_address::integer) *100/3 + ELSE + (s_v_name::integer + s_v_categories::integer + s_v_address::integer + + s_v_email::integer + s_v_phone::integer + s_v_hours::integer) *100/6 + END INTO critical_percent; + + INSERT INTO stakeholder ( + tenant_id, + name, address_1, address_2, city, state, zip, + phone, latitude, longitude, + website, inactive, notes, requirements, admin_notes, created_login_id, + parent_organization, physical_access, email, + items, services, facebook, twitter, pinterest, linkedin, description, + submitted_date, submitted_login_id, approved_date, reviewed_login_id, + assigned_date, assigned_login_id, claimed_date, claimed_login_id, + review_notes, instagram, admin_contact_name, + admin_contact_phone, admin_contact_email, + donation_contact_name, donation_contact_phone, + donation_contact_email, donation_pickup, + donation_accept_frozen, donation_accept_refrigerated, + donation_accept_perishable, donation_schedule, + donation_delivery_instructions, donation_notes, covid_notes, + category_notes, eligibility_notes, food_types, languages, + v_name, v_categories, v_address, + v_phone, v_email, v_hours, v_food_types, + verification_status_id, inactive_temporary, + hours, category_ids, neighborhood_id, complete_critical_percent, + food_bakery, food_dry_goods , food_produce, food_dairy, food_prepared, food_meat) + VALUES ( + s_tenant_id, + s_name, s_address_1, s_address_2, s_city, s_state, s_zip, + s_phone, s_latitude, s_longitude, + s_website, s_inactive, s_notes, s_requirements, s_admin_notes, s_created_login_id, + s_parent_organization, s_physical_access, s_email, + s_items, s_services, s_facebook, s_twitter, s_pinterest, s_linkedin, s_description, + s_submitted_date, s_submitted_login_id, s_approved_date, s_reviewed_login_id, + s_assigned_date, s_assigned_login_id, s_claimed_date, s_claimed_login_id, + s_review_notes, s_instagram, s_admin_contact_name, + s_admin_contact_phone, s_admin_contact_email, + s_donation_contact_name, s_donation_contact_phone, + s_donation_contact_email, s_donation_pickup, + s_donation_accept_frozen, s_donation_accept_refrigerated, + s_donation_accept_perishable, s_donation_schedule, + s_donation_delivery_instructions, s_donation_notes, s_covid_notes, + s_category_notes, s_eligibility_notes, s_food_types, s_languages, + s_v_name, s_v_categories, s_v_address, + s_v_phone, s_v_email, s_v_hours, s_v_food_types, + s_verification_status_id, s_inactive_temporary, + hours_array, categories, + (SELECT id FROM neighborhood WHERE ST_Contains(geometry, ST_Point(s_longitude, s_latitude)) LIMIT 1), + critical_percent, + s_food_bakery, s_food_dry_goods , s_food_produce, s_food_dairy, s_food_prepared, s_food_meat + ) RETURNING id INTO s_id; + + -- insert new stakeholder category(s) + FOREACH cat IN ARRAY categories + LOOP + INSERT INTO stakeholder_category + (stakeholder_id, category_id) + VALUES (s_id, cat); + END LOOP; + + -- insert new schedule(s) + FOREACH hours_element IN ARRAY hours_array + LOOP + INSERT INTO stakeholder_schedule( + stakeholder_id, day_of_week, open, close, week_of_month + ) VALUES( + s_id, + hours_element.day_of_week, + hours_element.open::time without time zone, + hours_element.close::time without time zone, + hours_element.week_of_month + ); + END LOOP; + COMMIT; + END; + $BODY$; + `); + + pgm.sql(` + DROP PROCEDURE IF EXISTS public.update_stakeholder( + varchar,varchar,varchar,varchar,varchar, + varchar,varchar,numeric,numeric,varchar,bool, + varchar,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar, + varchar,int4,timestamptz,int4,timestamp,int4, + timestamp,int4,timestamp,int4,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar, + bool,bool,bool,bool,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool, + bool,bool,int4,bool,int4,_int4,_stakeholder_hours); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + int4,varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric,numeric, + varchar,bool,varchar,varchar,varchar,int4,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,timestamptz,int4,timestamp,int4,timestamp, + int4,timestamp,int4,varchar,varchar,varchar,varchar,varchar,varchar,varchar, + varchar,bool,bool,bool,bool,varchar,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool,_int4,_stakeholder_hours,int4); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric,numeric,varchar, + bool,varchar,varchar,varchar,int4,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,timestamptz,int4,timestamp,int4, + timestamp,int4,timestamp,int4,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,bool,bool,bool,bool,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool,_int4, + _stakeholder_hours,int4); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + int4,varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric,numeric, + varchar,bool,varchar,varchar,varchar,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar,timestamptz,int4,timestamp, + int4,timestamp,int4,timestamp,int4,varchar,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,bool,bool,bool,bool,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool,_int4, + _stakeholder_hours); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric,numeric, + varchar,bool,varchar,varchar,varchar,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar,timestamptz,int4,timestamp, + timestamp,int4,timestamp,int4,timestamp,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool, + _int4,_stakeholder_hours); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + int4,int4,varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric, + numeric,varchar,bool,varchar,varchar,varchar,int4,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar,varchar,timestamptz,int4, + timestamp,int4,timestamp,int4,timestamp,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool, + _int4,_stakeholder_hours); + + DROP PROCEDURE IF EXISTS public.create_stakeholder( + varchar,varchar,varchar,varchar,varchar,varchar,varchar,numeric,numeric, + varchar,bool,varchar,varchar,varchar,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,varchar,timestamptz,int4,timestamp, + timestamp,int4,timestamp,int4,timestamp,int4,varchar,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,bool,bool,bool,bool,varchar,varchar,varchar, + varchar,varchar,varchar,varchar,varchar,bool,bool,bool,bool,bool,bool,int4,bool, + _int4,_stakeholder_hours,int4); + + `); + + pgm.sql(` + CREATE OR REPLACE FUNCTION public.on_insert_or_update_stakeholder() + RETURNS trigger + LANGUAGE 'plpgsql' + COST 100 + VOLATILE NOT LEAKPROOF + AS $BODY$ + DECLARE + best_row stakeholder_log%ROWTYPE; + latest_version INTEGER; + is_verified BOOLEAN := false; + categoryid INTEGER; + BEGIN + INSERT INTO public.stakeholder_log + (id, tenant_id, version, name, address_1, address_2, city, state, zip, + phone, latitude, longitude, website, fm_id, notes, + created_date, created_login_id, modified_date, modified_login_id, + requirements, admin_notes, inactive, parent_organization, + physical_access, email, items, services, facebook, twitter, + pinterest, + linkedin, + description, + approved_date, + reviewed_login_id, + assigned_login_id, + agency_type, + assigned_date, + review_notes, + claimed_login_id, + claimed_date, + instagram, + admin_contact_name, + admin_contact_phone, + admin_contact_email, + donation_contact_name, + donation_contact_phone, + donation_contact_email, + donation_pickup, + donation_accept_frozen, + donation_accept_refrigerated, + donation_accept_perishable, + donation_schedule, + donation_delivery_instructions, + covid_notes, + donation_notes, + category_notes, + eligibility_notes, + food_types, + languages, + verification_status_id, + inactive_temporary, + v_name, v_categories, v_address, v_email, v_phone, v_hours, v_food_types, + hours, category_ids, + neighborhood_id, + complete_critical_percent, + food_bakery, food_dry_goods , food_produce, food_dairy, food_prepared, food_meat + ) + VALUES ( + NEW.id, NEW.tenant_id, + (SELECT greatest(max(version) + 1, 1) FROM public.stakeholder_log where id = NEW.id), + NEW.name, + NEW.address_1, + NEW.address_2, + NEW.city, + NEW.state, + NEW.zip, + NEW.phone, + NEW.latitude, + NEW.longitude, + NEW.website, + NEW.fm_id, + NEW.notes, + NEW.created_date, + NEW.created_login_id, + NEW.modified_date, + NEW.modified_login_id, + NEW.requirements, + NEW.admin_notes, + NEW.inactive, + NEW.parent_organization, + NEW.physical_access, + NEW.email, + NEW.items, + NEW.services, + NEW.facebook, + NEW.twitter, + NEW.pinterest, + NEW.linkedin, + NEW.description, + NEW.approved_date, + NEW.reviewed_login_id, + NEW.assigned_login_id, + NEW.agency_type, + NEW.assigned_date, + NEW.review_notes, + NEW.claimed_login_id, + NEW.claimed_date, + NEW.instagram, + NEW.admin_contact_name, + NEW.admin_contact_phone, + NEW.admin_contact_email, + NEW.donation_contact_name, + NEW.donation_contact_phone, + NEW.donation_contact_email, + NEW.donation_pickup, + NEW.donation_accept_frozen, + NEW.donation_accept_refrigerated, + NEW.donation_accept_perishable, + NEW.donation_schedule, + NEW.donation_delivery_instructions, + NEW.covid_notes, + NEW.donation_notes, + NEW.category_notes, + NEW.eligibility_notes, + NEW.food_types, + NEW.languages, + NEW.verification_status_id, + NEW.inactive_temporary, + NEW.v_name, + NEW.v_categories, + NEW.v_address, + NEW.v_email, + NEW.v_phone, + NEW.v_hours, + NEW.v_food_types, + NEW.hours, + NEW.category_ids, + (SELECT id FROM neighborhood WHERE ST_Contains(geometry, ST_Point(NEW.longitude, NEW.latitude)) LIMIT 1), + NEW.complete_critical_percent, + NEW.food_bakery, NEW.food_dry_goods , NEW.food_produce, NEW.food_dairy, NEW.food_prepared, NEW.food_meat + ) RETURNING version INTO latest_version; + + -- We might need to select a new row as our "best" row for this stakeholder. + -- "best" is defined as the highest version in stakeholder_log with verification_status_id=4 + -- (4 means "verified"). + -- Barring that, the highest version is the "best". + + SELECT * INTO best_row FROM stakeholder_log + WHERE id=NEW.id + AND verification_status_id=4 + AND version=(select MAX(version) from stakeholder_log where id=NEW.id AND verification_status_id=4); + + -- Is there anything in best_row? (there might not be, if there are no verified rows) + IF NOT FOUND THEN + -- Fall back on finding the highest version number, which *just so happens* to be this row! + SELECT * INTO best_row FROM stakeholder_log + WHERE id=NEW.id + AND version=latest_version; + ELSE + is_verified = true; + END IF; + + IF FOUND THEN + DELETE FROM stakeholder_best where id=best_row.id; + INSERT INTO stakeholder_best + (id, tenant_id, name, address_1, address_2, city, state, zip, + phone, latitude, longitude, website, fm_id, notes, + created_date, created_login_id, modified_date, modified_login_id, + requirements, admin_notes, inactive, parent_organization, + physical_access, email, items, services, facebook, twitter, + pinterest, + linkedin, + description, + approved_date, + reviewed_login_id, + assigned_login_id, + agency_type, + assigned_date, + review_notes, + claimed_login_id, + claimed_date, + instagram, + admin_contact_name, + admin_contact_phone, + admin_contact_email, + donation_contact_name, + donation_contact_phone, + donation_contact_email, + donation_pickup, + donation_accept_frozen, + donation_accept_refrigerated, + donation_accept_perishable, + donation_schedule, + donation_delivery_instructions, + covid_notes, + donation_notes, + category_notes, + eligibility_notes, + food_types, + languages, + verification_status_id, + inactive_temporary, + v_name, v_categories, v_address, v_email, v_phone, v_hours, v_food_types, + hours, category_ids, + neighborhood_id, + complete_critical_percent, + food_bakery, food_dry_goods , food_produce, food_dairy, food_prepared, food_meat, + is_verified + ) + VALUES ( + best_row.id, + best_row.tenant_id, + best_row.name, + best_row.address_1, + best_row.address_2, + best_row.city, + best_row.state, + best_row.zip, + best_row.phone, + best_row.latitude, + best_row.longitude, + best_row.website, + best_row.fm_id, + best_row.notes, + best_row.created_date, + best_row.created_login_id, + best_row.modified_date, + best_row.modified_login_id, + best_row.requirements, + best_row.admin_notes, + best_row.inactive, + best_row.parent_organization, + best_row.physical_access, + best_row.email, + best_row.items, + best_row.services, + best_row.facebook, + best_row.twitter, + best_row.pinterest, + best_row.linkedin, + best_row.description, + best_row.approved_date, + best_row.reviewed_login_id, + best_row.assigned_login_id, + best_row.agency_type, + best_row.assigned_date, + best_row.review_notes, + best_row.claimed_login_id, + best_row.claimed_date, + best_row.instagram, + best_row.admin_contact_name, + best_row.admin_contact_phone, + best_row.admin_contact_email, + best_row.donation_contact_name, + best_row.donation_contact_phone, + best_row.donation_contact_email, + best_row.donation_pickup, + best_row.donation_accept_frozen, + best_row.donation_accept_refrigerated, + best_row.donation_accept_perishable, + best_row.donation_schedule, + best_row.donation_delivery_instructions, + best_row.covid_notes, + best_row.donation_notes, + best_row.category_notes, + best_row.eligibility_notes, + best_row.food_types, + best_row.languages, + best_row.verification_status_id, + best_row.inactive_temporary, + best_row.v_name, + best_row.v_categories, + best_row.v_address, + best_row.v_email, + best_row.v_phone, + best_row.v_hours, + best_row.v_food_types, + best_row.hours, + best_row.category_ids, + best_row.neighborhood_id, + best_row.complete_critical_percent, + best_row.food_bakery, best_row.food_dry_goods , best_row.food_produce, + best_row.food_dairy, best_row.food_prepared, best_row.food_meat, + is_verified); + + /* Populate normalized stakeholder_best_category table */ + IF best_row.category_ids IS NOT NULL THEN + FOREACH categoryid IN ARRAY best_row.category_ids + LOOP + INSERT INTO stakeholder_best_category + (stakeholder_id, category_id) + VALUES (best_row.id, categoryid); + END LOOP; + END IF; + ELSE + -- should probably log some sort of error, because this should never happen + RAISE EXCEPTION 'Could not find a best version of stakeholder id %', NEW.id; + END IF; + + RETURN NEW; + END; + $BODY$; + `); +}; + +exports.down = () => { + // not reversible +}; diff --git a/package-lock.json b/package-lock.json index bc16de5f6..d0f90e4fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "foodoasis", - "version": "1.0.20", + "version": "1.0.26", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3c2bcad63..7af381ae1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foodoasis", - "version": "1.0.26", + "version": "1.0.28", "author": "Hack for LA", "description": "Web API Server for Food Oasis", "main": "server.js",