diff --git a/app/controllers/stakeholder-best-controller.js b/app/controllers/stakeholder-best-controller.js new file mode 100644 index 000000000..cd1088d35 --- /dev/null +++ b/app/controllers/stakeholder-best-controller.js @@ -0,0 +1,43 @@ +const stakeholderService = require("../services/stakeholder-best-service"); + +const search = (req, res) => { + let categoryIds = req.query.categoryIds; + if (!req.query.latitude || !req.query.longitude) { + res + .status(404) + .json("Bad request: needs latitude and longitude parameters"); + } + if (!categoryIds) { + // If no filter, just use active categories. + categoryIds = ["1", "3", "8", "9", "10", "11", "12"]; + } else if (typeof categoryIds == "string") { + categoryIds = [categoryIds]; + } + const params = { ...req.query, categoryIds }; + stakeholderService + .search(params) + .then((resp) => { + res.send(resp); + }) + .catch((err) => { + console.log(err); + res.status("404").json({ error: err.toString() }); + }); +}; + +const getById = (req, res) => { + const { id } = req.params; + stakeholderService + .selectById(id) + .then((resp) => { + res.send(resp); + }) + .catch((err) => { + res.status("500").json({ error: err.toString() }); + }); +}; + +module.exports = { + search, + getById, +}; diff --git a/app/controllers/stakeholder-controller.js b/app/controllers/stakeholder-controller.js index 66166c6ce..bc04868d0 100644 --- a/app/controllers/stakeholder-controller.js +++ b/app/controllers/stakeholder-controller.js @@ -3,31 +3,6 @@ const { Readable } = require("stream"); const stringify = require("csv-stringify"); const search = (req, res) => { - let categoryIds = req.query.categoryIds; - if (!req.query.latitude || !req.query.longitude) { - res - .status(404) - .json("Bad request: needs latitude and longitude parameters"); - } - if (!categoryIds) { - // If no filter, just use active categories. - categoryIds = ["1", "3", "8", "9", "10", "11", "12"]; - } else if (typeof categoryIds == "string") { - categoryIds = [categoryIds]; - } - const params = { ...req.query, categoryIds }; - stakeholderService - .search(params) - .then((resp) => { - res.send(resp); - }) - .catch((err) => { - console.log(err); - res.status("404").json({ error: err.toString() }); - }); -}; - -const searchDashboard = (req, res) => { if (req.distance && (!req.latitude || !req.longitude)) { res .status(404) @@ -42,7 +17,7 @@ const searchDashboard = (req, res) => { } const params = { ...req.query, categoryIds }; stakeholderService - .searchDashboard(params) + .search(params) .then((resp) => { res.send(resp); }) @@ -210,9 +185,8 @@ const claim = (req, res) => { module.exports = { search, - searchDashboard, - csv, getById, + csv, post, put, remove, diff --git a/app/controllers/stakeholder-log-controller.js b/app/controllers/stakeholder-log-controller.js new file mode 100644 index 000000000..f99682f8c --- /dev/null +++ b/app/controllers/stakeholder-log-controller.js @@ -0,0 +1,17 @@ +const stakeholderLogService = require("../services/stakeholder-log-service"); + +const getById = (req, res) => { + const { id } = req.params; + stakeholderLogService + .selectById(id) + .then((resp) => { + res.send(resp); + }) + .catch((err) => { + res.status("500").json({ error: err.toString() }); + }); +}; + +module.exports = { + getById, +}; diff --git a/app/routes/index.js b/app/routes/index.js index 5f4a48c02..aa81c7eb5 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -8,6 +8,8 @@ const suggestionRouter = require("./suggestion-router"); const faqRouter = require("./faq-router"); const stakeholderRouter = require("./stakeholder-router"); +const stakeholderBestRouter = require("./stakeholder-best-router"); +const stakeholderLogRouter = require("./stakeholder-log-router"); const importRouter = require("./import-router"); const loadRouter = require("./load-router"); const esriRouter = require("./esri-router"); @@ -15,6 +17,8 @@ const esriRouter = require("./esri-router"); module.exports = router; router.use("/api/stakeholders", stakeholderRouter); +router.use("/api/stakeholderbests", stakeholderBestRouter); +router.use("/api/stakeholderlogs", stakeholderLogRouter); router.use("/api/tenants", tenantRouter); router.use("/api/accounts", accountRouter); router.use("/api/categories", categoryRouter); diff --git a/app/routes/stakeholder-best-router.js b/app/routes/stakeholder-best-router.js new file mode 100644 index 000000000..327a1caed --- /dev/null +++ b/app/routes/stakeholder-best-router.js @@ -0,0 +1,7 @@ +const router = require("express").Router(); +const stakeholderBestController = require("../controllers/stakeholder-best-controller"); + +router.get("/", stakeholderBestController.search); +router.get("/:id", stakeholderBestController.getById); + +module.exports = router; diff --git a/app/routes/stakeholder-log-router.js b/app/routes/stakeholder-log-router.js new file mode 100644 index 000000000..7ea488f0a --- /dev/null +++ b/app/routes/stakeholder-log-router.js @@ -0,0 +1,6 @@ +const router = require("express").Router(); +const stakeholderLogController = require("../controllers/stakeholder-log-controller"); + +router.get("/:id", stakeholderLogController.getById); + +module.exports = router; diff --git a/app/routes/stakeholder-router.js b/app/routes/stakeholder-router.js index 595cae8d7..870ed20aa 100644 --- a/app/routes/stakeholder-router.js +++ b/app/routes/stakeholder-router.js @@ -2,15 +2,14 @@ const router = require("express").Router(); const stakeholderController = require("../controllers/stakeholder-controller"); const jwtSession = require("../../middleware/jwt-session"); -router.get("/", stakeholderController.search); router.get( - "/dashboard", + "/", jwtSession.validateUserHasRequiredRoles([ "admin", "data_entry", "coordinator", ]), - stakeholderController.searchDashboard + stakeholderController.search ); router.get( "/:id", diff --git a/app/services/account-service.js b/app/services/account-service.js index 523b66696..0b269866d 100644 --- a/app/services/account-service.js +++ b/app/services/account-service.js @@ -1,6 +1,5 @@ const { pool } = require("./postgres-pool"); const { promisify } = require("util"); -const { toSqlBoolean } = require("./postgres-utils"); const moment = require("moment"); const bcrypt = require("bcrypt"); const { @@ -32,8 +31,8 @@ const selectAll = () => { }; const selectById = (id) => { - const sql = `select * from login where id = ${id}`; - return pool.query(sql).then((res) => { + const sql = `select * from login where id = $1`; + return pool.query(sql, [id]).then((res) => { const row = res.rows[0]; return { id: row.id, @@ -51,8 +50,8 @@ const selectById = (id) => { }; const selectByEmail = (email) => { - const sql = `select * from login where email ilike '${email}'`; - return pool.query(sql).then((res) => { + const sql = `select * from login where email ilike $1`; + return pool.query(sql, [email]).then((res) => { const row = res.rows[0]; if (row) { return { @@ -80,9 +79,13 @@ const register = async (model) => { try { const sql = `insert into login (first_name, last_name, email, password_hash) - values ('${firstName}', '${lastName}', '${email}', - '${model.passwordHash}') returning id`; - const insertResult = await pool.query(sql); + values ($1, $2, $3, $4) returning id`; + const insertResult = await pool.query(sql, [ + firstName, + lastName, + email, + model.passwordHash, + ]); result = { isSuccess: true, code: "REG_SUCCESS", @@ -104,8 +107,8 @@ const register = async (model) => { const resendConfirmationEmail = async (email) => { let result = null; try { - const sql = `select id from login where email = '${email}'`; - const insertResult = await pool.query(sql); + const sql = `select id from login where email ilike $1`; + const insertResult = await pool.query(sql, [email]); result = { success: true, code: "REG_SUCCESS", @@ -131,8 +134,8 @@ const requestRegistrationConfirmation = async (email, result) => { const token = uuid4(); try { const sqlToken = `insert into security_token (token, email) - values ('${token}', '${email}') `; - await pool.query(sqlToken); + values ($1, $2) `; + await pool.query(sqlToken, [token, email]); await sendRegistrationConfirmation(email, token); return result; } catch (err) { @@ -146,9 +149,9 @@ const requestRegistrationConfirmation = async (email, result) => { const confirmRegistration = async (token) => { const sql = `select email, date_created - from security_token where token = '${token}'`; + from security_token where token = $1;`; try { - const sqlResult = await pool.query(sql); + const sqlResult = await pool.query(sql, [token]); const now = moment(); if (sqlResult.rows.length < 1) { @@ -171,8 +174,8 @@ const confirmRegistration = async (token) => { const email = sqlResult.rows[0].email; const confirmSql = `update login set email_confirmed = true - where email = '${email}'`; - await pool.query(confirmSql); + where email ilike $1`; + await pool.query(confirmSql, [email]); return { success: true, @@ -191,8 +194,8 @@ const forgotPassword = async (model) => { const { email } = model; let result = null; try { - const sql = `select id from login where email = '${email}'`; - const checkAccountResult = await pool.query(sql); + const sql = `select id from login where email ilike $1`; + const checkAccountResult = await pool.query(sql, [email]); if ( checkAccountResult && checkAccountResult.rows && @@ -226,8 +229,8 @@ const requestResetPasswordConfirmation = async (email, result) => { const token = uuid4(); try { const sqlToken = `insert into security_token (token, email) - values ('${token}', '${email}') `; - await pool.query(sqlToken); + values ($1, $2); `; + await pool.query(sqlToken, [token, email]); result = await sendResetPasswordConfirmation(email, token); return result; } catch (err) { @@ -242,11 +245,11 @@ const requestResetPasswordConfirmation = async (email, result) => { // Verify password reset token and change password const resetPassword = async ({ token, password }) => { const sql = `select email, date_created - from security_token where token = '${token}'`; + from security_token where token = $1; `; const now = moment(); let email = ""; try { - const sqlResult = await pool.query(sql); + const sqlResult = await pool.query(sql, [token]); if (sqlResult.rows.length < 1) { return { @@ -268,9 +271,9 @@ const resetPassword = async ({ token, password }) => { const passwordHash = await promisify(bcrypt.hash)(password, SALT_ROUNDS); email = sqlResult.rows[0].email; const resetSql = `update login - set password_hash = '${passwordHash}' - where email = '${email}'`; - await pool.query(resetSql); + set password_hash = $1 + where email ilike $2 ;`; + await pool.query(resetSql, [passwordHash, email]); return { isSuccess: true, @@ -347,17 +350,17 @@ const authenticate = async (email, password) => { const update = (model) => { const { id, firstName, lastName } = model; const sql = `update login - set firstName = '${firstName}', - lastName = '${lastName}' - where id = ${id}`; - return pool.query(sql).then((res) => { + set firstName = $1, + lastName = $2 + where id = $3;`; + return pool.query(sql, [firstName, lastName, id]).then((res) => { return res; }); }; const remove = (id) => { - const sql = `delete from login where id = ${id}`; - return pool.query(sql).then((res) => { + const sql = `delete from login where id = $1`; + return pool.query(sql, [id]).then((res) => { return res; }); }; @@ -399,10 +402,8 @@ 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}=${toSqlBoolean( - booleanValue - )} where id = ${userId}`; - await pool.query(updateSql); + const updateSql = `update login set ${permissionName}=$1} where id = ${userId};`; + await pool.query(updateSql, [booleanValue]); return { success: true, code: "UPDATE_SUCCESS", diff --git a/app/services/category-service.js b/app/services/category-service.js index e5f6ada2d..c2305c142 100644 --- a/app/services/category-service.js +++ b/app/services/category-service.js @@ -2,7 +2,7 @@ const { pool } = require("./postgres-pool"); const selectAll = () => { const sql = ` - select id, name, inactive + select id, name, display_order as displayOrder, inactive from category order by name `; @@ -12,7 +12,7 @@ const selectAll = () => { }; const selectById = (id) => { - const sql = `select id, name, inactive + const sql = `select id, name, display_order as displayOrder, inactive from category where id = ${id}`; return pool.query(sql).then((res) => { return res.rows[0]; diff --git a/app/services/esri-service.js b/app/services/esri-service.js index a37f7e65e..747ef024d 100644 --- a/app/services/esri-service.js +++ b/app/services/esri-service.js @@ -1,7 +1,7 @@ const axios = require("axios"); -const getTokenUrl = `http://www.arcgis.com/sharing/oauth2/token`; -const findAddressCandidateUrl = `http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates`; +const getTokenUrl = `https://www.arcgis.com/sharing/oauth2/token`; +const findAddressCandidateUrl = `https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates`; let esriToken = ""; diff --git a/app/services/stakeholder-best-service.js b/app/services/stakeholder-best-service.js new file mode 100644 index 000000000..d56fc8ada --- /dev/null +++ b/app/services/stakeholder-best-service.js @@ -0,0 +1,377 @@ +const { pool } = require("./postgres-pool"); + +/* + +This service is for getting data from the stakeholder_best table, which +is what we want for the food seeker UI. It represents the "best" version of +each stakeholder to show end users, which is either the last approved version +(as indicated by is_verified = true) or if no version of the stakeholder +record has been approved, it will be the most recent version (i.e., have + the same values as the stakeholder table for that id.) + +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. + +*/ + +const booleanEitherClause = (columnName, value) => { + return value === "true" + ? ` and ${columnName} is true ` + : value === "false" + ? ` and ${columnName} is false ` + : ""; +}; + +const search = async ({ + categoryIds, + latitude, + longitude, + distance, + 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, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as created_date, s.created_login_id, + to_char(s.modified_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as modified_date, s.modified_login_id, + to_char(s.submitted_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as submitted_date, s.submitted_login_id, + to_char(s.approved_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as approved_date, s.reviewed_login_id, + to_char(s.assigned_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as assigned_date, s.assigned_login_id, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') + as claimed_date, s.claimed_login_id, + s.requirements, s.admin_notes, s.inactive, + s.parent_organization, s.physical_access, s.email, + s.items, s.services, s.facebook, + s.twitter, s.pinterest, s.linkedin, s.description, + 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.verification_status_id, s.inactive_temporary, + array_to_json(s.hours) as hours, s.category_ids, + s.neighborhood_id, s.is_verified, + ${locationClause ? `${locationClause} AS distance,` : ""} + ${buildLoginSelectsClause()} + from stakeholder_set as s + ${buildLoginJoinsClause()} + where s.tenant_id = ${tenantId} + ${ + Number(distance) && locationClause + ? `AND ${locationClause} < ${distance}` + : "" + } + ${booleanEitherClause("s.inactive", isInactive)} + ${ + Number(verificationStatusId) > 0 + ? ` and s.verification_status_id = ${verificationStatusId} ` + : "" + } + order by distance + `; + // console.log(sql); + let stakeholders = []; + let categoriesResults = []; + var stakeholderResult, stakeholder_ids; + try { + stakeholderResult = await pool.query(sql); + stakeholder_ids = stakeholderResult.rows.map((a) => a.id); + + if (stakeholder_ids.length) { + // Hoover up all the stakeholder categories + // for all of our stakeholder row results. + const categoriesSql = `select sc.stakeholder_id, c.id, c.name, c.display_order + from category c + join stakeholder_category sc on c.id = sc.category_id + where sc.stakeholder_id in (${stakeholder_ids.join(",")}) + order by c.display_order, c.name`; + categoriesResults = await pool.query(categoriesSql); + } + } catch (err) { + return Promise.reject(err.message); + } + + stakeholderResult.rows.forEach((row) => { + stakeholders.push({ + id: row.id, + name: row.name || "", + address1: row.address_1 || "", + address2: row.address_2 || "", + city: row.city || "", + state: row.state || "", + zip: row.zip || "", + phone: row.phone || "", + latitude: row.latitude ? Number(row.latitude) : null, + longitude: row.longitude ? Number(row.longitude) : null, + distance: row.distance ? Number(row.distance) : null, + website: row.website || "", + notes: row.notes || "", + createdDate: row.created_date, + createdLoginId: row.created_login_id, + modifiedDate: row.modified_date, + modifiedLoginId: row.modified_login_id, + submittedDate: row.submitted_date, + submittedLoginId: row.submitted_login_id, + assignedDate: row.assigned_date, + assignedLoginId: row.assigned_login_id, + approvedDate: row.approved_date, + reviewedLoginId: row.reviewed_login_id, + claimedDate: row.claimed_date, + claimedLoginId: row.claimed_login_id, + requirements: row.requirements || "", + adminNotes: row.admin_notes || "", + inactive: row.inactive, + createdUser: row.created_user || "", + modifiedUser: row.modified_user || "", + submittedUser: row.submitted_user || "", + reviewedUser: row.reviewed_user || "", + assignedUser: row.assigned_user || "", + claimedUser: row.claimed_user || "", + categories: categoriesResults.rows.filter( + (cats) => cats.stakeholder_id == row.id + ), + hours: row.hours || [], + parentOrganization: row.parent_organization || "", + physicalAccess: row.physical_access || "", + email: row.email || "", + items: row.items || "", + services: row.services || "", + facebook: row.facebook || "", + twitter: row.twitter || "", + pinterest: row.pinterest || "", + linkedin: row.linkedin || "", + description: row.description, + reviewNotes: row.review_notes, + instagram: row.instagram || "", + adminContactName: row.admin_contact_name || "", + adminContactPhone: row.admin_contact_phone || "", + adminContactEmail: row.admin_contact_email || "", + donationContactName: row.donation_contact_name || "", + donationContactPhone: row.donation_contact_phone || "", + donationContactEmail: row.donation_contact_email || "", + donationPickup: row.donation_pickup || false, + donationAcceptFrozen: row.donation_accept_frozen || false, + donationAcceptRefrigerated: row.donation_accept_refrigerated || false, + donationAcceptPerishable: row.donation_accept_perishable || false, + donationSchedule: row.donation_schedule || "", + donationDeliveryInstructions: row.donation_delivery_instructions || "", + donationNotes: row.donation_notes || "", + covidNotes: row.covid_notes || "", + categoryNotes: row.category_notes || "", + eligibilityNotes: row.eligibility_notes || "", + foodTypes: row.food_types || "", + languages: row.languages || "", + confirmedName: row.v_name, + confirmedCategories: row.v_categories, + confirmedAddress: row.v_address, + confirmedPhone: row.v_phone, + confirmedEmail: row.v_email, + confirmedHours: row.v_hours, + verificationStatusId: row.verification_status_id, + inactiveTemporary: row.inactive_temporary, + neighborhoodId: row.neighborhood_id, + is_verified: row.is_verified, + }); + }); + + return stakeholders; +}; + +const selectById = async (id) => { + const sql = `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, + s.hours, + (select array(select row_to_json(category_row) + from ( + select c.id, c.name, c.display_order + from category c + join stakeholder_best_category sc on c.id = sc.category_id + where sc.stakeholder_id = s.id + order by c.display_order + ) category_row + )) as categories, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as created_date, s.created_login_id, + to_char(s.modified_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as modified_date, s.modified_login_id, + to_char(s.submitted_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as submitted_date, s.submitted_login_id, + to_char(s.approved_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as approved_date, s.reviewed_login_id, + to_char(s.assigned_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS')as assigned_date, s.assigned_login_id, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as claimed_date, s.claimed_login_id, + s.requirements::varchar, s.admin_notes, s.inactive, + s.parent_organization, s.physical_access, s.email, + s.items, s.services, s.facebook, + s.twitter, s.pinterest, s.linkedin, s.description, + 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.verification_status_id, s.inactive_temporary, + s.neighborhood_id, s.is_verified, + ${buildLoginSelectsClause()} + from stakeholder_best s + ${buildLoginJoinsClause()} + where s.id = ${id}`; + const result = await pool.query(sql); + const row = result.rows[0]; + const stakeholder = { + id: row.id, + name: row.name || "", + address1: row.address_1 || "", + address2: row.address_2 || "", + city: row.city || "", + state: row.state || "", + zip: row.zip || "", + phone: row.phone || "", + latitude: row.latitude ? Number(row.latitude) : null, + longitude: row.longitude ? Number(row.longitude) : null, + website: row.website || "", + createdDate: row.created_date, + notes: row.notes || "", + createdLoginId: row.created_login_id, + modifiedDate: row.modified_date, + modifiedLoginId: row.modified_login_id, + submittedDate: row.submitted_date, + submittedLoginId: row.submitted_login_id, + approvedDate: row.approved_date, + reviewedLoginId: row.approved_login_id, + assignedLoginId: row.assigned_login_id, + assignedDate: row.assigned_date, + claimedLoginId: row.claimed_login_id, + claimedDate: row.claimed_date, + requirements: row.requirements || "", + adminNotes: row.admin_notes || "", + inactive: row.inactive, + createdUser: row.created_user || "", + modifiedUser: row.modified_user || "", + submittedUser: row.submitted_user || "", + reviewedUser: row.reviewed_user || "", + assignedUser: row.assigned_user || "", + claimedUser: row.claimed_user || "", + categories: row.categories, + hours: row.hours, + parentOrganization: row.parent_organization || "", + physicalAccess: row.physical_access || "", + email: row.email || "", + items: row.items || "", + services: row.services || "", + facebook: row.facebook || "", + twitter: row.twitter || "", + pinterest: row.pinterest || "", + linkedin: row.linkedin || "", + description: row.description, + reviewNotes: row.review_notes, + instagram: row.instagram || "", + adminContactName: row.admin_contact_name || "", + adminContactPhone: row.admin_contact_phone || "", + adminContactEmail: row.admin_contact_email || "", + donationContactName: row.donation_contact_name || "", + donationContactPhone: row.donation_contact_phone || "", + donationContactEmail: row.donation_contact_email || "", + donationPickup: row.donation_pickup || false, + donationAcceptFrozen: row.donation_accept_frozen || false, + donationAcceptRefrigerated: row.donation_accept_refrigerated || false, + donationAcceptPerishable: row.donation_accept_perishable || false, + donationSchedule: row.donation_schedule || "", + donationDeliveryInstructions: row.donation_delivery_instructions || "", + donationNotes: row.donation_notes || "", + covidNotes: row.covid_notes || "", + categoryNotes: row.category_notes || "", + eligibilityNotes: row.eligibility_notes || "", + foodTypes: row.food_types || "", + languages: row.languages || "", + confirmedName: row.v_name, + confirmedCategories: row.v_categories, + confirmedAddress: row.v_address, + confirmedPhone: row.v_phone, + confirmedEmail: row.v_email, + confirmedHours: row.v_hours, + verificationStatusId: row.verification_status_id, + inactiveTemporary: row.inactive_temporary, + neighborhoodId: row.neighborhood_id, + is_verified: row.is_verified, + }; + + // Don't have a distance, since we didn't specify origin + stakeholder.distance = null; + + return stakeholder; +}; + +const buildCTEClause = (categoryIds, name) => { + const categoryClause = categoryIds + ? `stakeholder_category_set AS ( + select * from stakeholder_best_category + where stakeholder_best_category.category_id in (${categoryIds.join( + "," + )})),` + : ""; + const nameClause = "'%" + name.replace(/'/g, "''") + "%'"; + const cteClause = `WITH ${categoryClause} + stakeholder_set AS ( + select * from stakeholder_best + where name ilike ${nameClause} + and id in ( + select stakeholder_id from stakeholder_category_set + ) + )`; + return cteClause; +}; + +const buildLocationClause = (latitude, longitude) => { + var locationClause = ""; + if (latitude && longitude) { + locationClause = + " point(" + + longitude + + ", " + + latitude + + ") <@> point(s.longitude, s.latitude) "; + } + return locationClause; +}; + +const buildLoginJoinsClause = () => { + return ` + left join login L1 on s.created_login_id = L1.id + left join login L2 on s.modified_login_id = L2.id + left join login L3 on s.submitted_login_id = L3.id + left join login L4 on s.reviewed_login_id = L4.id + left join login L5 on s.assigned_login_id = L5.id + left join login L6 on s.claimed_login_id = L6.id + `; +}; + +const buildLoginSelectsClause = () => { + return ` + concat(L1.first_name, ' ', L1.last_name) as created_user, + concat(L2.first_name, ' ', L2.last_name) as modified_user, + concat(L3.first_name, ' ', L3.last_name) as submitted_user, + concat(L4.first_name, ' ', L4.last_name) as reviewed_user, + concat(L5.first_name, ' ', L5.last_name) as assigned_user, + concat(L6.first_name, ' ', L6.last_name) as claimed_user + `; +}; + +module.exports = { + search, + selectById, +}; diff --git a/app/services/stakeholder-log-service.js b/app/services/stakeholder-log-service.js new file mode 100644 index 000000000..dfa9d7bcc --- /dev/null +++ b/app/services/stakeholder-log-service.js @@ -0,0 +1,145 @@ +const { pool } = require("./postgres-pool"); +const categoryService = require("./category-service"); + +/* +This service is for getting data from the stakeholder_log table for +viewing the audit log for a particular stakeholder. +*/ + +const selectById = async (id) => { + const sql = `select + s.id, s.version, s.name, s.address_1, s.address_2, s.city, s.state, s.zip, + s.phone, s.latitude, s.longitude, s.website, s.notes, + s.hours, + s.category_ids, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as created_date, s.created_login_id, + to_char(s.modified_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as modified_date, s.modified_login_id, + to_char(s.submitted_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as submitted_date, s.submitted_login_id, + to_char(s.approved_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as approved_date, s.reviewed_login_id, + to_char(s.assigned_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS')as assigned_date, s.assigned_login_id, + to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') as claimed_date, s.claimed_login_id, + s.requirements::varchar, s.admin_notes, s.inactive, + s.parent_organization, s.physical_access, s.email, + s.items, s.services, s.facebook, + s.twitter, s.pinterest, s.linkedin, s.description, + 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.verification_status_id, s.inactive_temporary, + s.neighborhood_id, + ${buildLoginSelectsClause()} + from stakeholder_log s + ${buildLoginJoinsClause()} + where s.id = $1 + order by s.version desc`; + + let stakeholders = []; + let categories = []; + var stakeholderResult; + try { + stakeholderResult = await pool.query(sql, [id]); + categories = await categoryService.selectAll(); + // stakeholder_ids = stakeholderResult.rows.map((a) => a.id); + // if (stakeholder_ids.length) { + // // Hoover up all the stakeholder categories + // // for all of our stakeholder row results. + // const categoriesSql = `select sc.stakeholder_id, c.id, c.name, c.display_order + // from category c + // join stakeholder_category sc on c.id = sc.category_id + // where sc.stakeholder_id in (${stakeholder_ids.join(",")}) + // order by c.display_order`; + // categoriesResults = await pool.query(categoriesSql); + //} + } catch (err) { + return Promise.reject(err.message); + } + + stakeholderResult.rows.forEach((row) => { + stakeholders.push({ + id: row.id, + version: row.version, + name: row.name || "", + address1: row.address_1 || "", + address2: row.address_2 || "", + city: row.city || "", + state: row.state || "", + zip: row.zip || "", + phone: row.phone || "", + latitude: row.latitude ? Number(row.latitude) : null, + longitude: row.longitude ? Number(row.longitude) : null, + distance: row.distance ? Number(row.distance) : null, + website: row.website || "", + createdDate: row.created_date, + createdLoginId: row.created_login_id, + modifiedDate: row.modified_date, + modifiedLoginId: row.modified_login_id, + submittedDate: row.submitted_date, + submittedLoginId: row.submitted_login_id, + assignedDate: row.assigned_date, + assignedLoginId: row.assigned_login_id, + approvedDate: row.approved_date, + reviewedLoginId: row.reviewed_login_id, + claimedDate: row.claimed_date, + claimedLoginId: row.claimed_login_id, + requirements: row.requirements || "", + adminNotes: row.admin_notes || "", + inactive: row.inactive, + createdUser: row.created_user || "", + modifiedUser: row.modified_user || "", + submittedUser: row.submitted_user || "", + reviewedUser: row.reviewed_user || "", + assignedUser: row.assigned_user || "", + claimedUser: row.claimed_user || "", + categories: row.category_ids + .map((cat) => categories.find((c) => c.id === cat)) + .sort((c) => c.displayOrder), + email: row.email || "", + covidNotes: row.covid_notes || "", + confirmedName: row.v_name, + confirmedCategories: row.v_categories, + confirmedAddress: row.v_address, + confirmedPhone: row.v_phone, + confirmedEmail: row.v_email, + confirmedHours: row.v_hours, + verificationStatusId: row.verification_status_id, + inactiveTemporary: row.inactive_temporary, + neighborhoodId: row.neighborhood_id, + neighborhoodName: row.neighborhood_name, + completeCriticalPercent: row.complete_critical_percent, + }); + }); + + return stakeholders; +}; + +const buildLoginJoinsClause = () => { + return ` + left join login L1 on s.created_login_id = L1.id + left join login L2 on s.modified_login_id = L2.id + left join login L3 on s.submitted_login_id = L3.id + left join login L4 on s.reviewed_login_id = L4.id + left join login L5 on s.assigned_login_id = L5.id + left join login L6 on s.claimed_login_id = L6.id + `; +}; + +const buildLoginSelectsClause = () => { + return ` + concat(L1.first_name, ' ', L1.last_name) as created_user, + concat(L2.first_name, ' ', L2.last_name) as modified_user, + concat(L3.first_name, ' ', L3.last_name) as submitted_user, + concat(L4.first_name, ' ', L4.last_name) as reviewed_user, + concat(L5.first_name, ' ', L5.last_name) as assigned_user, + concat(L6.first_name, ' ', L6.last_name) as claimed_user + `; +}; + +module.exports = { + selectById, +}; diff --git a/app/services/stakeholder-service.js b/app/services/stakeholder-service.js index 1e92b2c39..694a0d63b 100644 --- a/app/services/stakeholder-service.js +++ b/app/services/stakeholder-service.js @@ -6,6 +6,18 @@ const { toSqlTimestamp, } = require("./postgres-utils"); +/* + +This service is for getting data from the stakeholder table, which +is what we want for the administration UI. It represents the most recent +draft version of each stakeholder for data entry and administrators +to work with. + +If you make changes to the database structure, be sure to update these +methods as well as the corresponding methods in the stakeholder-best-service.js. + +*/ + const trueFalseEitherClause = (columnName, value) => { return value === "true" ? ` and ${columnName} is not null ` @@ -23,176 +35,6 @@ const booleanEitherClause = (columnName, value) => { }; const search = async ({ - categoryIds, - latitude, - longitude, - distance, - isInactive, - verificationStatusId, - tenantId, -}) => { - const locationClause = buildLocationClause(latitude, longitude); - const categoryClause = buildCTEClause(categoryIds, "", true); // true indicates we want to search - // stakeholder_best table, not stakeholder - - 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, - to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as created_date, s.created_login_id, - to_char(s.modified_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as modified_date, s.modified_login_id, - to_char(s.submitted_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as submitted_date, s.submitted_login_id, - to_char(s.approved_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as approved_date, s.reviewed_login_id, - to_char(s.assigned_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as assigned_date, s.assigned_login_id, - to_char(s.created_date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') - as claimed_date, s.claimed_login_id, - s.requirements, s.admin_notes, s.inactive, - s.parent_organization, s.physical_access, s.email, - s.items, s.services, s.facebook, - s.twitter, s.pinterest, s.linkedin, s.description, - 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.verification_status_id, s.inactive_temporary, - array_to_json(s.hours) as hours, s.category_ids, - s.neighborhood_id, s.is_verified, - ${locationClause ? `${locationClause} AS distance,` : ""} - ${buildLoginSelectsClause()} - from stakeholder_set as s - ${buildLoginJoinsClause()} - where s.tenant_id = ${tenantId} - ${ - Number(distance) && locationClause - ? `AND ${locationClause} < ${distance}` - : "" - } - ${booleanEitherClause("s.inactive", isInactive)} - ${ - Number(verificationStatusId) > 0 - ? ` and s.verification_status_id = ${verificationStatusId} ` - : "" - } - order by distance - `; - // console.log(sql); - let stakeholders = []; - let categoriesResults = []; - var stakeholderResult, stakeholder_ids; - try { - stakeholderResult = await pool.query(sql); - stakeholder_ids = stakeholderResult.rows.map((a) => a.id); - - if (stakeholder_ids.length) { - // Hoover up all the stakeholder categories - // for all of our stakeholder row results. - const categoriesSql = `select sc.stakeholder_id, c.id, c.name, c.display_order - from category c - join stakeholder_category sc on c.id = sc.category_id - where sc.stakeholder_id in (${stakeholder_ids.join(",")}) - order by c.display_order, c.name`; - categoriesResults = await pool.query(categoriesSql); - } - } catch (err) { - return Promise.reject(err.message); - } - - stakeholderResult.rows.forEach((row) => { - stakeholders.push({ - id: row.id, - name: row.name || "", - address1: row.address_1 || "", - address2: row.address_2 || "", - city: row.city || "", - state: row.state || "", - zip: row.zip || "", - phone: row.phone || "", - latitude: row.latitude ? Number(row.latitude) : null, - longitude: row.longitude ? Number(row.longitude) : null, - distance: row.distance ? Number(row.distance) : null, - website: row.website || "", - notes: row.notes || "", - createdDate: row.created_date, - createdLoginId: row.created_login_id, - modifiedDate: row.modified_date, - modifiedLoginId: row.modified_login_id, - submittedDate: row.submitted_date, - submittedLoginId: row.submitted_login_id, - assignedDate: row.assigned_date, - assignedLoginId: row.assigned_login_id, - approvedDate: row.approved_date, - reviewedLoginId: row.reviewed_login_id, - claimedDate: row.claimed_date, - claimedLoginId: row.claimed_login_id, - requirements: row.requirements || "", - adminNotes: row.admin_notes || "", - inactive: row.inactive, - createdUser: row.created_user || "", - modifiedUser: row.modified_user || "", - submittedUser: row.submitted_user || "", - reviewedUser: row.reviewed_user || "", - assignedUser: row.assigned_user || "", - claimedUser: row.claimed_user || "", - categories: categoriesResults.rows.filter( - (cats) => cats.stakeholder_id == row.id - ), - hours: row.hours || [], - parentOrganization: row.parent_organization || "", - physicalAccess: row.physical_access || "", - email: row.email || "", - items: row.items || "", - services: row.services || "", - facebook: row.facebook || "", - twitter: row.twitter || "", - pinterest: row.pinterest || "", - linkedin: row.linkedin || "", - description: row.description, - reviewNotes: row.review_notes, - instagram: row.instagram || "", - adminContactName: row.admin_contact_name || "", - adminContactPhone: row.admin_contact_phone || "", - adminContactEmail: row.admin_contact_email || "", - donationContactName: row.donation_contact_name || "", - donationContactPhone: row.donation_contact_phone || "", - donationContactEmail: row.donation_contact_email || "", - donationPickup: row.donation_pickup || false, - donationAcceptFrozen: row.donation_accept_frozen || false, - donationAcceptRefrigerated: row.donation_accept_refrigerated || false, - donationAcceptPerishable: row.donation_accept_perishable || false, - donationSchedule: row.donation_schedule || "", - donationDeliveryInstructions: row.donation_delivery_instructions || "", - donationNotes: row.donation_notes || "", - covidNotes: row.covid_notes || "", - categoryNotes: row.category_notes || "", - eligibilityNotes: row.eligibility_notes || "", - foodTypes: row.food_types || "", - languages: row.languages || "", - confirmedName: row.v_name, - confirmedCategories: row.v_categories, - confirmedAddress: row.v_address, - confirmedPhone: row.v_phone, - confirmedEmail: row.v_email, - confirmedHours: row.v_hours, - verificationStatusId: row.verification_status_id, - inactiveTemporary: row.inactive_temporary, - neighborhoodId: row.neighborhood_id, - is_verified: row.is_verified, - }); - }); - - return stakeholders; -}; - -const searchDashboard = async ({ tenantId, name, categoryIds, @@ -408,9 +250,9 @@ const selectById = async (id) => { 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.neighborhood_id, s.is_verified, + s.neighborhood_id, ${buildLoginSelectsClause()} - from stakeholder_best s + from stakeholder s ${buildLoginJoinsClause()} where s.id = ${id}`; const result = await pool.query(sql); @@ -490,7 +332,6 @@ const selectById = async (id) => { verificationStatusId: row.verification_status_id, inactiveTemporary: row.inactive_temporary, neighborhoodId: row.neighborhood_id, - is_verified: row.is_verified, }; // Don't have a distance, since we didn't specify origin @@ -1069,23 +910,16 @@ const remove = async (id) => { } }; -// we can either search in the stakeholder or stakeholder_best -// table, as indicated by useBest -const buildCTEClause = (categoryIds, name, useBest) => { +const buildCTEClause = (categoryIds, name) => { const categoryClause = categoryIds ? `stakeholder_category_set AS ( - select * from ${ - useBest ? "stakeholder_best_category" : "stakeholder_category" - } - WHERE ${ - useBest ? "stakeholder_best_category" : "stakeholder_category" - }.category_id in (${categoryIds.join(",")})),` + select * from stakeholder_category + WHERE stakeholder_category.category_id in (${categoryIds.join(",")})),` : ""; const nameClause = "'%" + name.replace(/'/g, "''") + "%'"; const cteClause = `WITH ${categoryClause} stakeholder_set AS ( - select * from ${useBest ? `stakeholder_best` : `stakeholder`} - where name ilike ${nameClause} + select * from stakeholder where name ilike ${nameClause} and id in ( select stakeholder_id from stakeholder_category_set ) @@ -1134,7 +968,6 @@ const buildLoginSelectsClause = () => { module.exports = { search, - searchDashboard, selectById, selectCsv, insert, diff --git a/client/package-lock.json b/client/package-lock.json index 7b54b62a2..be803c288 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "food-oasis-client", - "version": "1.0.20", + "version": "1.0.21", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9369,9 +9369,9 @@ } }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, "node-int64": { "version": "0.4.0", @@ -12380,11 +12380,11 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { diff --git a/client/package.json b/client/package.json index eccf8eb7c..224e51c33 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.21", + "version": "1.0.22S", "author": "Hack for LA", "license": "GPL-2.0", "private": true, diff --git a/client/src/App.js b/client/src/App.js index 44b95cd94..45ae033bb 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -10,11 +10,10 @@ import { getTenantId } from "helpers/Configuration"; import { UserContext } from "components/user-context"; import Toast from "components/Toast"; import Header from "components/Header"; -import StakeholdersContainer from "components/Stakeholder/StakeholdersContainer"; import VerificationAdmin from "components/Verification/VerificationAdmin"; import VerificationDashboard from "components/Verification/VerificationDashboard"; import SecurityAdminDashboard from "components/SecurityAdminDashboard/SecurityAdminDashboard"; -import StakeholderEdit from "components/Stakeholder/StakeholderEdit"; +import OrganizationEdit from "components/Verification/OrganizationEdit"; import Donate from "components/StaticPages/Donate"; import About from "components/StaticPages/About"; import Faq from "components/StaticPages/Faq"; @@ -47,7 +46,7 @@ const useStyles = makeStyles({ overflowY: "scroll", flexGrow: 1, }, - stakeholderEditWrapper: { + OrganizationEditWrapper: { flexBasis: "90%", paddingTop: "1em", paddingBottom: "1em", @@ -182,16 +181,10 @@ function App() { - - - -
- +
+
diff --git a/client/src/components/Icon.js b/client/src/components/Icon.js new file mode 100644 index 000000000..bda02bf7d --- /dev/null +++ b/client/src/components/Icon.js @@ -0,0 +1,30 @@ +import React from "react"; +import PantryIcon from "images/pantryIcon"; +import MealIcon from "images/mealIcon"; +import SplitPantryMealIcon from "images/splitPantryMealIcon"; +import { + MEAL_PROGRAM_CATEGORY_ID, + FOOD_PANTRY_CATEGORY_ID, +} from "constants/stakeholder"; + +const Icon = ({ stakeholder, height, width }) => { + let isClosed = false; + if (!stakeholder || !stakeholder.categories || !stakeholder.categories.length) + return
; + if (stakeholder.inactiveTemporary || stakeholder.inactive) isClosed = true; + + return stakeholder.categories.some( + (category) => category.id === FOOD_PANTRY_CATEGORY_ID + ) && + stakeholder.categories.some( + (category) => category.id === MEAL_PROGRAM_CATEGORY_ID + ) ? ( + + ) : stakeholder.categories[0].id === FOOD_PANTRY_CATEGORY_ID ? ( + + ) : ( + + ); +}; + +export default Icon; diff --git a/client/src/components/Map.js b/client/src/components/Map.js index bb77f5805..e8090dd9c 100644 --- a/client/src/components/Map.js +++ b/client/src/components/Map.js @@ -1,7 +1,6 @@ import React from "react"; import ReactMapGL, { NavigationControl } from "react-map-gl"; -import { MAPBOX_TOKEN } from "secrets"; import { MAPBOX_STYLE } from "constants/map"; import MarkerPopup from "./MarkerPopup"; @@ -56,7 +55,7 @@ function Map({ selectedLatitude, selectedLongitude, stakeholders }) { width={`90vw`} height={`82vh`} onViewportChange={(newViewport) => setViewport(newViewport)} - mapboxApiAccessToken={MAPBOX_TOKEN} + mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN} mapStyle={MAPBOX_STYLE} > {/* TODO: uncomment GeolocateControl when you can figure out why it's crashing the page now */} diff --git a/client/src/components/Results/ResultsContainer.js b/client/src/components/Results/ResultsContainer.js index 53d6ff91e..7c13d1800 100644 --- a/client/src/components/Results/ResultsContainer.js +++ b/client/src/components/Results/ResultsContainer.js @@ -2,9 +2,9 @@ import React, { useEffect, useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import { Grid } from "@material-ui/core"; -import { useOrganizations } from "hooks/useOrganizations/useOrganizations"; +import { useOrganizationBests } from "hooks/useOrganizationBests"; import useCategoryIds from "hooks/useCategoryIds"; -import isMobile from "helpers/isMobile"; +import { isMobile } from "helpers"; import Filters from "./ResultsFilters"; import List from "./ResultsList"; @@ -35,7 +35,7 @@ export default function ResultsContainer(props) { // Component state const storage = window.sessionStorage; const { userCoordinates, userSearch, setToast } = props; - const { data, search } = useOrganizations(); + const { data, search } = useOrganizationBests(); const [sortedData, setSortedData] = useState([]); const classes = useStyles(); diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index ecc641365..30a446cf9 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -16,7 +16,7 @@ import { FOOD_PANTRY_CATEGORY_ID, DEFAULT_CATEGORIES, } from "constants/stakeholder"; -import isMobile from "helpers/isMobile"; +import { isMobile } from "helpers"; import SwitchViewsButton from "components/SwitchViewsButton"; import Search from "components/Search"; @@ -152,9 +152,7 @@ const ResultsFilters = ({ const doHandleSearch = useCallback( (e) => { - if (e) { - e.preventDefault(); - } + if (e) e.preventDefault(); const storage = window.sessionStorage; search({ latitude: @@ -214,7 +212,7 @@ const ResultsFilters = ({ useEffect(() => { doHandleSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [radius, categoryIds, isVerifiedSelected, toggleCategory]); + }, [origin, radius, categoryIds, isVerifiedSelected, toggleCategory]); const handleDistanceChange = (distance) => { setRadius(distance); diff --git a/client/src/components/Results/ResultsList.js b/client/src/components/Results/ResultsList.js index a1dd8568e..16c345ace 100644 --- a/client/src/components/Results/ResultsList.js +++ b/client/src/components/Results/ResultsList.js @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import { Grid } from "@material-ui/core"; import { makeStyles } from "@material-ui/core/styles"; import StakeholderPreview from "components/Stakeholder/StakeholderPreview"; -import isMobile from "helpers/isMobile"; +import { isMobile } from "helpers"; const useStyles = makeStyles((theme, props) => ({ list: { @@ -15,7 +15,7 @@ const useStyles = makeStyles((theme, props) => ({ display: "flex", flexDirection: "column", alignItems: "center", - padding: "0 2em", + padding: "0 1em", [theme.breakpoints.up("md")]: { height: "100%", }, diff --git a/client/src/components/Results/ResultsMap.js b/client/src/components/Results/ResultsMap.js index e011fec1e..ae8204058 100644 --- a/client/src/components/Results/ResultsMap.js +++ b/client/src/components/Results/ResultsMap.js @@ -4,10 +4,9 @@ import ReactMapGL, { NavigationControl } from "react-map-gl"; import { Grid } from "@material-ui/core"; import { makeStyles } from "@material-ui/core/styles"; -import { MAPBOX_TOKEN } from "secrets"; import { MAPBOX_STYLE } from "constants/map"; import { DEFAULT_CATEGORIES } from "constants/stakeholder"; -import isMobile from "helpers/isMobile"; +import { isMobile } from "helpers"; import StakeholderPreview from "components/Stakeholder/StakeholderPreview"; import StakeholderDetails from "components/Stakeholder/StakeholderDetails"; @@ -97,7 +96,7 @@ function Map({ width="100%" height="100%" onViewportChange={(newViewport) => setViewport(newViewport)} - mapboxApiAccessToken={MAPBOX_TOKEN} + mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN} mapStyle={MAPBOX_STYLE} onClick={() => doSelectStakeholder(null)} > diff --git a/client/src/components/Search.js b/client/src/components/Search.js index de82eb7d4..a4137060f 100644 --- a/client/src/components/Search.js +++ b/client/src/components/Search.js @@ -55,8 +55,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function Search(props) { - const { userCoordinates, setOrigin, origin } = props; +export default function Search({ userCoordinates, setOrigin, origin }) { const classes = useStyles(); const [selectedPlace, setSelectedPlace] = useState(""); const [newInputValue, updateNewInputValue] = useState(origin?.locationName); @@ -78,11 +77,9 @@ export default function Search(props) { }); const handleInputChange = (event) => { + if (!event.target.value) return; setSelectedPlace(event.target.value); updateNewInputValue(event.target.value); - if (!event.target.value) { - return; - } fetchMapboxResults(event.target.value); }; @@ -105,9 +102,7 @@ export default function Search(props) { } }; - const renderInput = (inputProps) => { - const { InputProps, classes } = inputProps; - + const renderInput = ({ InputProps, classes }) => { return ( { - const { - highlightedIndex, - selectedItem, - inputValue, - mapboxResults, - getItemProps, - } = params; - + const renderResults = ({ + highlightedIndex, + selectedItem, + inputValue, + mapboxResults, + getItemProps, + }) => { if (!inputValue && userCoordinates && userCoordinates.latitude) { return ( {renderInput({ classes, - selectedItem, - availableItems: mapboxResults, InputProps: { ...getInputProps({ - onClick: () => toggleMenu(), + onClick: () => { + setSelectedPlace(""); + updateNewInputValue(""); + toggleMenu(); + }, onChange: handleInputChange, value: newInputValue && !selectedPlace diff --git a/client/src/components/Stakeholder/StakeholderCriteria.js b/client/src/components/Stakeholder/StakeholderCriteria.js deleted file mode 100644 index c1afcf8ea..000000000 --- a/client/src/components/Stakeholder/StakeholderCriteria.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { makeStyles } from "@material-ui/core/styles"; -import { Card, CardContent, Grid } from "@material-ui/core"; -import { List, ListItem, Typography } from "@material-ui/core"; -import { EditButton } from "components/Buttons"; -import SwitchViewsButton from "components/SwitchViewsButton"; - -const useStyles = makeStyles((theme) => ({ - card: { - margin: "0px", - }, -})); - -function StakeholderCriteria({ - selectedCategories, - latitude, - longitude, - selectedLocationName, - selectedDistance, - searchString, - isMapView, - switchResultsView, - openSearchPanel, -}) { - const classes = useStyles(); - if (!selectedCategories) return null; - return ( - - - - - { - openSearchPanel(true); - }} - /> - - - - - - - - - {selectedCategories.map((cat) => cat.name).join(", ")} - - - - - - {selectedDistance > 0 - ? ` Within ${selectedDistance} mi of ` - : ` By distance from `} - - - {selectedLocationName - ? selectedLocationName - : `(${longitude}, ${latitude})`} - - - - - - {searchString - ? ` Containing "${searchString}" in the name` - : null} - - - - - - - - ); -} - -export default StakeholderCriteria; diff --git a/client/src/components/Stakeholder/StakeholderDetails.js b/client/src/components/Stakeholder/StakeholderDetails.js index 71f1ad1cf..3e9d863a5 100644 --- a/client/src/components/Stakeholder/StakeholderDetails.js +++ b/client/src/components/Stakeholder/StakeholderDetails.js @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; + import mapMarker from "images/mapMarker"; import fbIcon from "images/fbIcon.png"; import instaIcon from "images/instaIcon.png"; @@ -9,9 +10,11 @@ import { VERIFICATION_STATUS, } from "constants/stakeholder"; import { ORGANIZATION_COLORS, CLOSED_COLOR } from "constants/map"; +import { extractNumbers, getGoogleMapsUrl } from "helpers"; + +import Icon from "components/Icon"; import SuggestionDialog from "components/SuggestionDialog"; import { PlainButton } from "components/Buttons"; -import getIcon from "helpers/getIcon"; const useStyles = makeStyles((theme, props) => ({ stakeholder: { @@ -53,20 +56,6 @@ const useStyles = makeStyles((theme, props) => ({ title: { alignSelf: "flex-start", }, - link: { textDecoration: "none" }, - directions: { - alignSelf: "center", - display: "flex", - justifyContent: "center", - alignItems: "center", - color: "white", - width: "15em", - height: "3em", - fontSize: "14px", - fontWeight: "bold", - borderRadius: "10px", - margin: "1em 0 0 0", - }, hoursContainer: { width: "100%", display: "inherit", @@ -111,6 +100,16 @@ const useStyles = makeStyles((theme, props) => ({ padding: ".25em .5em", borderRadius: "3px", }, + buttons: { + width: "100%", + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + }, + numbers: { + display: "inline", + alignSelf: "flex-start", + }, })); const StakeholderDetails = ({ @@ -174,24 +173,23 @@ const StakeholderDetails = ({ } }; - const getGoogleMapsUrl = (zip, address1, address2) => { - const baseUrl = `https://google.com/maps/place/`; - - const address1urlArray = address1.split(" "); - const address1url = address1urlArray.reduce( - (acc, currentWord) => `${acc}+${currentWord}` - ); - - if (address2) { - const address2urlArray = address2.split(" "); - const address2url = address2urlArray.reduce( - (acc, currentWord) => `${acc}+${currentWord}` + const numbers = extractNumbers(selectedStakeholder.phone).map((n) => { + if (n.number) { + return ( + + {n.value} + ); - return `${baseUrl}${address1url},+${address2url},+${zip}`; + } else { + return {n.value} ; } - - return `${baseUrl}${address1url},+${zip}`; - }; + }); return (
@@ -204,7 +202,7 @@ const StakeholderDetails = ({ setToast={setToast} />
-
{getIcon(selectedStakeholder)}
+
{selectedStakeholder.name} {selectedStakeholder.address1} @@ -285,36 +283,21 @@ const StakeholderDetails = ({ : selectedStakeholder.createdDate.format("MMM Do, YYYY")}

) : null} -
- - - Directions - - +
+ + window.open( + getGoogleMapsUrl( + selectedStakeholder.zip, + selectedStakeholder.address1, + selectedStakeholder.address2 || null + ) + ) + } + /> Phone - {selectedStakeholder.phone ? ( - - - {selectedStakeholder.phone} - - + {numbers.length ? ( +
{numbers}
) : ( No Phone Number on record )} diff --git a/client/src/components/Stakeholder/StakeholderList.js b/client/src/components/Stakeholder/StakeholderList.js deleted file mode 100644 index 8e24ef641..000000000 --- a/client/src/components/Stakeholder/StakeholderList.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { withRouter } from "react-router-dom"; -import { Grid, Typography } from "@material-ui/core"; -import StakeholderListItem from "./StakeholderListItem"; - -const StakeholderList = (props) => { - const { stakeholders } = props; - return ( - - {stakeholders && - stakeholders.length > 0 && - stakeholders.map((stakeholder) => ( - - ))} - {!stakeholders || - (stakeholders.length < 1 && ( - - - No matches found, please try different Criteria - - - ))} - )} - - ); -}; - -export default withRouter(StakeholderList); diff --git a/client/src/components/Stakeholder/StakeholderListItem.js b/client/src/components/Stakeholder/StakeholderListItem.js deleted file mode 100644 index 924f15add..000000000 --- a/client/src/components/Stakeholder/StakeholderListItem.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import { Grid, Typography, Card, CardContent, Link } from "@material-ui/core"; -import { Check } from "@material-ui/icons"; -import { UserContext } from "components/user-context"; -import { EditButton } from "components/Buttons"; -import { withRouter } from "react-router-dom"; -import moment from "moment"; - -const StakeholderListItem = (props) => { - const { stakeholder } = props; - return ( - - - - - {stakeholder.name} - - - {stakeholder.categories.map((cat) => cat.name).join(", ")} - - - {stakeholder.website ? ( -
- - {stakeholder.website} - -
- ) : null} -
{stakeholder.address1}
- {stakeholder.address2 ?
{stakeholder.address2}
: null} -
- {stakeholder.city}, {stakeholder.state} {stakeholder.zip} -
- {/*
- longitude: {stakeholder.longitude} latitude: {stakeholder.latitude} -
*/} - {stakeholder.distance && ( -
distance: {stakeholder.distance.toFixed(2)} mi.
- )} -
- {stakeholder.verifiedDate ? ( - - - - - - - {" " + moment(stakeholder.verifiedDate).format("MM/DD/YY")} - - - - ) : ( - - )} - - {(user) => - user && (user.isAdmin || user.isCoordinator) ? ( -
- -
- ) : null - } -
-
-
-
-
- ); -}; - -export default withRouter(StakeholderListItem); diff --git a/client/src/components/Stakeholder/StakeholderPreview.js b/client/src/components/Stakeholder/StakeholderPreview.js index 6ea87d1b2..9565bd4af 100644 --- a/client/src/components/Stakeholder/StakeholderPreview.js +++ b/client/src/components/Stakeholder/StakeholderPreview.js @@ -2,13 +2,16 @@ import React from "react"; import PropTypes from "prop-types"; import moment from "moment"; import { makeStyles } from "@material-ui/core/styles"; + import { MEAL_PROGRAM_CATEGORY_ID, FOOD_PANTRY_CATEGORY_ID, } from "constants/stakeholder"; - import { ORGANIZATION_COLORS, CLOSED_COLOR } from "constants/map"; -import getIcon from "helpers/getIcon"; +import { getGoogleMapsUrl, extractNumbers } from "helpers"; + +import Icon from "components/Icon"; +import { PlainButton } from "components/Buttons"; const useStyles = makeStyles(() => ({ stakeholder: { @@ -19,31 +22,20 @@ const useStyles = makeStyles(() => ({ alignItems: "center", padding: "1em 0", }, - img: { - display: "flex", - marginRight: "1em", - width: "50px", - }, info: { fontSize: "1em", textAlign: "left", width: "100%", + display: "flex", flexDirection: "column", justifyContent: "space-between", "& p": { margin: 0, }, }, - check: { - width: "10%", - display: "flex", - flexDirection: "column", - justifyContent: "space-between", - }, label: { width: "100%", display: "flex", - flexDirection: "column", }, closedLabel: { color: "#545454", @@ -51,6 +43,7 @@ const useStyles = makeStyles(() => ({ backgroundColor: "#E0E0E0", padding: ".25em .5em", borderRadius: "3px", + margin: ".25em 0", }, openLabel: { color: "#fff", @@ -59,6 +52,7 @@ const useStyles = makeStyles(() => ({ padding: ".25em", borderRadius: "3px", margin: ".25em 0", + marginRight: "0.25em", }, closingSoonLabel: { color: "#fff", @@ -68,6 +62,22 @@ const useStyles = makeStyles(() => ({ borderRadius: "3px", margin: ".25em 0", }, + buttons: { + width: "100%", + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + }, + leftInfo: { + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + alignItems: "center", + marginRight: "1em", + }, + name: { + fontSize: "16px", + }, })); const isLastOccurrenceInMonth = (currentDay) => { @@ -128,6 +138,8 @@ const isAlmostClosed = (hours) => { const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { const classes = useStyles(); + const mainNumber = extractNumbers(stakeholder.phone).find((n) => n.number); + const stakeholderHours = stakeholdersCurrentDaysHours(stakeholder); const isOpenFlag = !!stakeholderHours; const isAlmostClosedFlag = isOpenFlag && isAlmostClosed(stakeholderHours); @@ -140,31 +152,39 @@ const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { key={stakeholder.id} onClick={() => doSelectStakeholder(stakeholder)} > -
{getIcon(stakeholder)}
+
+ +
+ {stakeholder.distance >= 10 + ? stakeholder.distance.toString().substring(0, 3).padEnd(4, "0") + : stakeholder.distance.toString().substring(0, 3)}{" "} + mi +
+
-

{stakeholder.name}

-

{stakeholder.address1}

-

- {stakeholder.city} {stakeholder.zip} -

- {stakeholder.categories.map((category) => ( - - {category.name} - - ))} +

{stakeholder.name}

+
+ {stakeholder.categories.map((category) => ( + + {category.name} + + ))} +
{stakeholder.inactiveTemporary || stakeholder.inactive ? ( @@ -187,12 +207,29 @@ const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { ) : null}
-
-
- {stakeholder.distance >= 10 - ? stakeholder.distance.toString().substring(0, 3).padEnd(4, "0") - : stakeholder.distance.toString().substring(0, 3)}{" "} - mi +
+ + window.open( + getGoogleMapsUrl( + stakeholder.zip, + stakeholder.address1, + stakeholder.address2 || null + ) + ) + } + size="small" + /> + {mainNumber && ( + window.open(`tel:${mainNumber.value}`)} + /> + )} + +
); diff --git a/client/src/components/Stakeholder/StakeholderSearch.js b/client/src/components/Stakeholder/StakeholderSearch.js deleted file mode 100644 index 31ab23a99..000000000 --- a/client/src/components/Stakeholder/StakeholderSearch.js +++ /dev/null @@ -1,299 +0,0 @@ -import React, { useState } from "react"; -import { makeStyles } from "@material-ui/core/styles"; -import { - Card, - CardContent, - Checkbox, - Input, - ListItemText, - MenuItem, - Select, - Grid, - TextField, - Chip, - FormLabel, - FormControlLabel, - Typography, - RadioGroup, - Radio, -} from "@material-ui/core"; -import { SearchButton } from "components/Buttons"; -import SwitchViewsButton from "components/SwitchViewsButton"; -import LocationAutocomplete from "components/LocationAutocomplete"; - -const useStyles = makeStyles((theme) => ({ - card: { - margin: "0px", - }, - chips: { - display: "flex", - flexWrap: "wrap", - }, - chip: { - margin: 2, - }, - formLabel: { - margin: "1rem 0 .5rem", - }, -})); - -const closeTo = (lat1, lon1, lat2, lon2) => { - return Math.abs(lat1 - lat2) + Math.abs(lon1 - lon2) < 0.01; -}; - -function StakeholderSearch(props) { - const [selectedCategories, setSelectedCategories] = useState( - props.selectedCategories - ); - const [searchString, setSearchString] = useState(props.searchString); - const [latitude] = useState(props.latitude); - const [longitude] = useState(props.longitude); - const [customLatitude, setCustomLatitude] = useState(props.selectedLatitude); - const [customLongitude, setCustomLongitude] = useState( - props.selectedLongitude - ); - const [customLocationName, setCustomLocationName] = useState( - props.selectedLocationName - ); - - const [selectedLatitude, setSelectedLatitude] = useState( - props.selectedLatitude - ); - const [selectedLongitude, setSelectedLongitude] = useState( - props.selectedLongitude - ); - const [selectedLocationName, setSelectedLocationName] = useState( - props.selectedLocationName - ); - const [selectedDistance, setSelectedDistance] = useState( - props.selectedDistance - ); - const [useMyLocation, setUseMyLocation] = useState( - latitude - ? closeTo( - props.selectedLatitude, - props.selectedLongitude, - props.latitude, - props.longitude - ) - ? "my" - : "custom" - : "custom" - ); - - const classes = useStyles(); - - const handleRadioChange = (evt) => { - const val = evt.target.value; - setUseMyLocation(val); - if (val === "my") { - setSelectedLatitude(latitude); - setSelectedLongitude(longitude); - setSelectedLocationName(""); - } else { - setSelectedLatitude(customLatitude); - setSelectedLongitude(customLongitude); - setSelectedLocationName(customLocationName); - } - }; - - const setLocation = (location) => { - setCustomLatitude(location.location.y); - setCustomLongitude(location.location.x); - setCustomLocationName(location.address); - setSelectedLatitude(location.location.y); - setSelectedLongitude(location.location.x); - setSelectedLocationName(location.address); - setUseMyLocation("custom"); - }; - - return ( - - - - - - { - props.search( - searchString, - selectedLatitude, - selectedLongitude, - selectedLocationName, - selectedCategories, - selectedDistance - ); - }} - /> - - - - - - - - Name - { - setSearchString(event.target.value); - }} - /> - - - Categories - } - renderValue={(selected) => ( -
- {selected.map((category) => ( - - ))} -
- )} - > - {props.categories.map((category) => ( - - cat.id) - .indexOf(category.id) > -1 - } - /> - - - ))} - -
-
- - Location - -
{"Within "}
- } - > - - (Any) - - - 1 - - - 2 - - - 3 - - - 5 - - - 10 - - - 20 - - - 50 - - -
{"miles of"}
-
- - {/* If we got location from browser, allow using current location */} - {latitude ? ( - - } - style={{ alignItems: "flex-start" }} - label={ -
- My Location: - {`(${latitude}, ${longitude})`} -
- } - /> - } - style={{ alignItems: "flex-start" }} - label={ -
-
- {`Custom Location:`} - {customLocationName ? ( - {customLocationName} - ) : null} - {customLatitude ? ( - {`(${customLatitude.toFixed( - 6 - )}, ${customLongitude.toFixed(6)})`} - ) : null} - - -
- } - /> -
- ) : ( -
- {customLocationName ? ( - {customLocationName} - ) : null} - {customLatitude ? ( - {`(${customLatitude.toFixed( - 6 - )}, ${customLongitude.toFixed(6)})`} - ) : null} - - -
- )} -
-
-
-
-
- ); -} - -export default StakeholderSearch; diff --git a/client/src/components/Stakeholder/StakeholdersContainer.js b/client/src/components/Stakeholder/StakeholdersContainer.js deleted file mode 100644 index c91eaafc1..000000000 --- a/client/src/components/Stakeholder/StakeholdersContainer.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from "react"; -import { withRouter } from "react-router-dom"; -import { Typography } from "@material-ui/core"; -import StakeholderSearch from "./StakeholderSearch"; -import StakeholderCriteria from "./StakeholderCriteria"; -import StakeholderList from "./StakeholderList"; -import Map from "components/Map"; -import { RotateLoader } from "react-spinners"; -import { useStakeholders } from "hooks/useStakeholders/useStakeholders"; - -const styles = { - container: { - display: "flex", - flexDirection: "column", - padding: "1rem", - }, - header: { - display: "flex", - }, -}; - -function StakeholdersContainer(props) { - const { history, userCoordinates } = props; - const { state, dispatch, actionTypes, search } = useStakeholders(history); - const [isMapView, setIsMapView] = React.useState(true); - const openSearchPanel = (isOpen) => { - dispatch({ type: actionTypes.TOGGLE_SEARCH_PANEL, isOpen }); - }; - - const { - stakeholders, - categories, - searchString, - selectedLongitude, - selectedLatitude, - selectedLocationName, - selectedDistance, - selectedCategories, - isSearchPanelOpen, - isLoading, - latitude, - longitude, - } = state; - - return ( -
-
- - Stakeholders{" "} - -
- <> - {isSearchPanelOpen ? ( - setIsMapView(!isMapView)} - isMapView={isMapView} - /> - ) : ( - setIsMapView(!isMapView)} - isMapView={isMapView} - /> - )} - {isSearchPanelOpen ? null : isLoading ? ( -
- -
- ) : isMapView && selectedLatitude && selectedLongitude ? ( - - ) : ( - - )} - -
- ); -} - -export default withRouter(StakeholdersContainer); diff --git a/client/src/components/Stakeholder/StakeholderEdit.js b/client/src/components/Verification/OrganizationEdit.js similarity index 99% rename from client/src/components/Stakeholder/StakeholderEdit.js rename to client/src/components/Verification/OrganizationEdit.js index d2cf2b04a..d0444025f 100644 --- a/client/src/components/Stakeholder/StakeholderEdit.js +++ b/client/src/components/Verification/OrganizationEdit.js @@ -129,7 +129,7 @@ const validationSchema = Yup.object().shape({ ), }); -const emptyStakeholder = { +const emptyOrganization = { id: 0, name: "", description: "", @@ -194,7 +194,7 @@ const emptyStakeholder = { inactiveTemporary: false, }; -const StakeholderEdit = (props) => { +const OrganizationEdit = (props) => { const { classes, setToast, match, user, history } = props; const editId = match.params.id; const [assignDialogOpen, setAssignDialogOpen] = useState(false); @@ -204,7 +204,7 @@ const StakeholderEdit = (props) => { const [tabPage, setTabPage] = useState(0); const [geocodeResults, setGeocodeResults] = useState([]); const [nextUrl, setNextUrl] = useState(null); - const [originalData, setOriginalData] = useState(emptyStakeholder); + const [originalData, setOriginalData] = useState(emptyOrganization); const { data: categories } = useCategories(); @@ -223,7 +223,7 @@ const StakeholderEdit = (props) => { setOriginalData(stakeholder); } else { - setOriginalData(emptyStakeholder); + setOriginalData(emptyOrganization); } } catch (err) { console.log(err); @@ -2017,7 +2017,7 @@ const StakeholderEdit = (props) => { ); }; -StakeholderEdit.propTypes = { +OrganizationEdit.propTypes = { classes: PropTypes.object, setToast: PropTypes.func, match: PropTypes.object, @@ -2025,4 +2025,4 @@ StakeholderEdit.propTypes = { history: PropTypes.object, }; -export default withStyles(styles)(withRouter(StakeholderEdit)); +export default withStyles(styles)(withRouter(OrganizationEdit)); diff --git a/client/src/components/Verification/VerificationAdmin.js b/client/src/components/Verification/VerificationAdmin.js index 2599736a0..a8e430749 100644 --- a/client/src/components/Verification/VerificationAdmin.js +++ b/client/src/components/Verification/VerificationAdmin.js @@ -8,7 +8,7 @@ import { SearchButton } from "../Buttons"; import { PrimaryButton } from "../../ui"; import StakeholderGrid from "./VerificationAdminGrid"; import { RotateLoader } from "react-spinners"; -import { useOrganizations } from "hooks/useOrganizations/useOrganizations"; +import { useOrganizations } from "hooks/useOrganizations"; import { useCategories } from "hooks/useCategories/useCategories"; import { useTenants } from "hooks/useTenants/useTenants"; import { useNeighborhoods } from "hooks/useNeighborhoods/useNeighborhoods"; @@ -153,7 +153,7 @@ function VerificationAdmin(props) { data: stakeholders, loading: stakeholdersLoading, error: stakeholdersError, - searchDashboard: stakeholderSearch, + search: stakeholderSearch, } = useOrganizations(); const searchCallback = useCallback(stakeholderSearch, []); diff --git a/client/src/components/Verification/VerificationDashboard.js b/client/src/components/Verification/VerificationDashboard.js index 9f42dc561..9ba3b15da 100644 --- a/client/src/components/Verification/VerificationDashboard.js +++ b/client/src/components/Verification/VerificationDashboard.js @@ -5,7 +5,7 @@ import { makeStyles } from "@material-ui/core/styles"; import { SearchButton, PlainButton } from "../Buttons"; import StakeholderGrid from "./VerificationAdminGrid"; import { RotateLoader } from "react-spinners"; -import { useOrganizations } from "hooks/useOrganizations/useOrganizations"; +import { useOrganizations } from "hooks/useOrganizations"; import * as stakeholderService from "services/stakeholder-service"; const useStyles = makeStyles((theme) => ({ @@ -80,7 +80,7 @@ function VerificationDashboard(props) { data: stakeholders, loading: stakeholdersLoading, error: stakeholdersError, - searchDashboard: stakeholderSearch, + search: stakeholderSearch, } = useOrganizations(); const searchCallback = useCallback(stakeholderSearch, []); diff --git a/client/src/containers/Home/index.js b/client/src/containers/Home/index.js index b8425ee99..9d41b0099 100644 --- a/client/src/containers/Home/index.js +++ b/client/src/containers/Home/index.js @@ -11,7 +11,7 @@ import Box from "@material-ui/core/Box"; import Search from "components/Search"; import logo from "images/foodoasisla.svg"; import logoCA from "images/foodoasisca.svg"; -import { getTenantId } from "../../helpers/Configuration"; +import { getTenantId } from "helpers/Configuration"; const useStyles = makeStyles((theme) => ({ container: { diff --git a/client/src/helpers/getIcon.js b/client/src/helpers/getIcon.js deleted file mode 100644 index 24782cd9f..000000000 --- a/client/src/helpers/getIcon.js +++ /dev/null @@ -1,25 +0,0 @@ -import pantryIcon from "../images/pantryIcon"; -import mealIcon from "../images/mealIcon"; -import splitPantryMealIcon from "../images/splitPantryMealIcon"; -import { - MEAL_PROGRAM_CATEGORY_ID, - FOOD_PANTRY_CATEGORY_ID, -} from "../constants/stakeholder"; - -const getIcon = (stakeholder) => { - let isClosed = false; - if (stakeholder.inactiveTemporary || stakeholder.inactive) isClosed = true; - - return stakeholder.categories.some( - (category) => category.id === FOOD_PANTRY_CATEGORY_ID - ) && - stakeholder.categories.some( - (category) => category.id === MEAL_PROGRAM_CATEGORY_ID - ) - ? splitPantryMealIcon(isClosed) - : stakeholder.categories[0].id === FOOD_PANTRY_CATEGORY_ID - ? pantryIcon(isClosed) - : mealIcon(isClosed); -}; - -export default getIcon; diff --git a/client/src/helpers/index.js b/client/src/helpers/index.js new file mode 100644 index 000000000..17e665da7 --- /dev/null +++ b/client/src/helpers/index.js @@ -0,0 +1,33 @@ +export const getGoogleMapsUrl = (zip, address1, address2) => { + const baseUrl = `https://google.com/maps/place/`; + + const address1urlArray = address1.split(" "); + const address1url = address1urlArray.reduce( + (acc, currentWord) => `${acc}+${currentWord}` + ); + + if (address2) { + const address2urlArray = address2.split(" "); + const address2url = address2urlArray.reduce( + (acc, currentWord) => `${acc}+${currentWord}` + ); + return `${baseUrl}${address1url},+${address2url},+${zip}`; + } + + return `${baseUrl}${address1url},+${zip}`; +}; + +export const isMobile = new RegExp("Mobi", "i").test(navigator.userAgent) + ? true + : false; + +export const extractNumbers = (numbers) => + numbers.split(/(and)|,|&+/).map((n) => { + const match = new RegExp( + "\\+?\\(?\\d*\\)? ?\\(?\\d+\\)?\\d*([\\s./-]?\\d{2,})+", + "g" + ).exec(n); + return match + ? { number: true, value: match[0] } + : { number: false, value: n }; + }); diff --git a/client/src/helpers/isMobile.js b/client/src/helpers/isMobile.js deleted file mode 100644 index 13526e87e..000000000 --- a/client/src/helpers/isMobile.js +++ /dev/null @@ -1,5 +0,0 @@ -const isMobile = new RegExp("Mobi", "i").test(navigator.userAgent) - ? true - : false; - -export default isMobile; diff --git a/client/src/hooks/useMapboxGeocoder.js b/client/src/hooks/useMapboxGeocoder.js index e124363e8..0cd853db0 100644 --- a/client/src/hooks/useMapboxGeocoder.js +++ b/client/src/hooks/useMapboxGeocoder.js @@ -1,16 +1,13 @@ import React from "react"; import axios from "axios"; import debounce from "debounce-fn"; -import { getTenantId } from "../helpers/Configuration"; +import { getTenantId } from "helpers/Configuration"; const baseUrl = `https://api.mapbox.com/geocoding/v5/mapbox.places`; const losAngelesCountyLatLong = "-118.9517,33.6988,-117.6462,34.8233"; const californiaLatLong = "-124.389, 32.4796, -114.1723, 42.072"; -const MAPBOX_TOKEN = - "pk.eyJ1IjoibHVjYXNob21lciIsImEiOiJjazFqcnRjcm0wNmZ1M2JwZXg2eDFzMXd3In0.yYpkKLrFCxF-qyBfZH1a8w"; - const initialState = { isLoading: false, error: false, @@ -52,7 +49,7 @@ export function useMapboxGeocoder() { async (searchString) => { const bbox = getTenantId() === 1 ? losAngelesCountyLatLong : californiaLatLong; - const mapboxUrl = `${baseUrl}/${searchString}.json?bbox=${bbox}&access_token=${MAPBOX_TOKEN}`; + const mapboxUrl = `${baseUrl}/${searchString}.json?bbox=${bbox}&access_token=${process.env.REACT_APP_MAPBOX_TOKEN}`; dispatch({ type: actionTypes.FETCH_REQUEST }); try { diff --git a/client/src/hooks/useOrganizationBests.js b/client/src/hooks/useOrganizationBests.js new file mode 100644 index 000000000..d4d8ed82a --- /dev/null +++ b/client/src/hooks/useOrganizationBests.js @@ -0,0 +1,49 @@ +import { useState } from "react"; +import * as stakeholderService from "../services/stakeholder-best-service"; + +export const useOrganizationBests = () => { + const [state, setState] = useState({ + data: null, + loading: false, + error: false, + }); + + const search = async ({ + name, + latitude, + longitude, + radius, + categoryIds, + isInactive, + verificationStatusId, + }) => { + if (!latitude || !longitude) { + setState({ data: null, loading: false, error: true }); + const msg = + "Call to search function missing latitude and/or longitude parameters"; + console.error(msg); + return Promise.reject(msg); + } + //if (!categoryIds || categoryIds.length === 0) return; + try { + setState({ data: null, loading: true, error: false }); + const stakeholders = await stakeholderService.search({ + name, + categoryIds, + latitude, + longitude, + distance: radius, + isInactive, + verificationStatusId, + }); + setState({ data: stakeholders, loading: false, error: false }); + return stakeholders; + } catch (err) { + setState({ data: null, loading: false, error: true }); + console.error(err); + return Promise.reject(err); + } + }; + + return { ...state, search }; +}; diff --git a/client/src/hooks/useOrganizations/useOrganizations.js b/client/src/hooks/useOrganizations.js similarity index 55% rename from client/src/hooks/useOrganizations/useOrganizations.js rename to client/src/hooks/useOrganizations.js index c188b14f1..97333acc9 100644 --- a/client/src/hooks/useOrganizations/useOrganizations.js +++ b/client/src/hooks/useOrganizations.js @@ -1,5 +1,5 @@ import { useState } from "react"; -import * as stakeholderService from "../../services/stakeholder-service"; +import * as stakeholderService from "../services/stakeholder-service"; export const useOrganizations = () => { const [state, setState] = useState({ @@ -9,43 +9,6 @@ export const useOrganizations = () => { }); const search = async ({ - name, - latitude, - longitude, - radius, - categoryIds, - isInactive, - verificationStatusId, - }) => { - if (!latitude || !longitude) { - setState({ data: null, loading: false, error: true }); - const msg = - "Call to search function missing latitude and/or longitude parameters"; - console.error(msg); - return Promise.reject(msg); - } - //if (!categoryIds || categoryIds.length === 0) return; - try { - setState({ data: null, loading: true, error: false }); - const stakeholders = await stakeholderService.search({ - name, - categoryIds, - latitude, - longitude, - distance: radius, - isInactive, - verificationStatusId, - }); - setState({ data: stakeholders, loading: false, error: false }); - return stakeholders; - } catch (err) { - setState({ data: null, loading: false, error: true }); - console.error(err); - return Promise.reject(err); - } - }; - - const searchDashboard = async ({ tenantId, name, latitude, @@ -68,7 +31,7 @@ export const useOrganizations = () => { }) => { try { setState({ data: null, loading: true, error: false }); - const stakeholders = await stakeholderService.searchDashboard({ + const stakeholders = await stakeholderService.search({ tenantId, name, latitude, @@ -98,5 +61,5 @@ export const useOrganizations = () => { } }; - return { ...state, search, searchDashboard }; + return { ...state, search }; }; diff --git a/client/src/hooks/useStakeholders/actionTypes.js b/client/src/hooks/useStakeholders/actionTypes.js deleted file mode 100644 index 0adb2a025..000000000 --- a/client/src/hooks/useStakeholders/actionTypes.js +++ /dev/null @@ -1,20 +0,0 @@ -export const actionTypes = { - STAKEHOLDERS: { - FETCH_REQUEST: "STAKEHOLDERS_FETCH_REQUEST", - FETCH_SUCCESS: "STAKEHOLDERS_FETCH_SUCCESS", - FETCH_FAILURE: "STAKEHOLDERS_FETCH_FAILURE", - }, - CATEGORIES: { - FETCH_REQUEST: "CATEGORIES_FETCH_REQUEST", - FETCH_SUCCESS: "CATEGORIES_FETCH_SUCCESS", - FETCH_FAILURE: "CATEGORIES_FETCH_FAILURE", - }, - LOCATION: { - FETCH_REQUEST: "LOCATION_FETCH_REQUEST", - FETCH_SUCCESS: "LOCATION_FETCH_SUCCESS", - FETCH_FAILURE: "LOCATION_FETCH_FAILURE", - }, - INITIALIZE_STATE: "INITIALIZE_STATE", - UPDATE_CRITERIA: "UPDATE_CRITERIA", - TOGGLE_SEARCH_PANEL: "TOGGLE_SEARCH_PANEL", -}; diff --git a/client/src/hooks/useStakeholders/initialState.js b/client/src/hooks/useStakeholders/initialState.js deleted file mode 100644 index a2f168554..000000000 --- a/client/src/hooks/useStakeholders/initialState.js +++ /dev/null @@ -1,19 +0,0 @@ -export const initialState = { - isLoading: false, - categoriesError: null, - stakeholdersError: null, - locationError: null, - isSearchPanelOpen: false, - stakeholders: [], - categories: [], - searchString: "", - selectedCategoryIds: [1, 8, 9], - selectedCategories: null, - latitude: null, - longitude: null, - selectedLatitude: 34.041001, - selectedLongitude: -118.235036, - selectedLocationName: "", - selectedDistance: 10, - queryParametersLoaded: false, -}; diff --git a/client/src/hooks/useStakeholders/reducer.js b/client/src/hooks/useStakeholders/reducer.js deleted file mode 100644 index 86352045f..000000000 --- a/client/src/hooks/useStakeholders/reducer.js +++ /dev/null @@ -1,61 +0,0 @@ -import { actionTypes } from "./actionTypes"; - -export function reducer(state, action) { - switch (action.type) { - case actionTypes.STAKEHOLDERS.FETCH_REQUEST: - return { ...state, isLoading: true }; - case actionTypes.STAKEHOLDERS.FETCH_SUCCESS: - return { - ...state, - stakeholders: action.stakeholders, - ...action.payload, - isLoading: false, - isSearchPanelOpen: false, - }; - case actionTypes.STAKEHOLDERS.FETCH_FAILURE: - return { ...state, stakeholdersError: action.error, isLoading: false }; - - case actionTypes.CATEGORIES.FETCH_REQUEST: - return { ...state, isLoading: true }; - case actionTypes.CATEGORIES.FETCH_SUCCESS: - return { - ...state, - categories: action.categories, - selectedCategories: action.selectedCategories, - isLoading: false, - }; - case actionTypes.CATEGORIES.FETCH_FAILURE: - return { - ...state, - categoriesError: action.error, - //isLoading: false - }; - - case actionTypes.LOCATION.FETCH_REQUEST: - return { ...state, isLoading: true }; - case actionTypes.LOCATION.FETCH_SUCCESS: - return { - ...state, - latitude: action.userCoordinates.latitude, - longitude: action.userCoordinates.longitude, - isLoading: false, - }; - case actionTypes.LOCATION.FETCH_FAILURE: - return { ...state, isLoading: false }; - - case actionTypes.INITIALIZE_STATE: - return { - ...state, - stakeholders: action.stakeholders, - ...action.payload, - isLoading: false, - isSearchPanelOpen: false, - }; - case actionTypes.TOGGLE_SEARCH_PANEL: - return { ...state, isSearchPanelOpen: action.isOpen }; - // case actionTypes.UPDATE_CRITERIA: - // return { ...state, ...action.payload }; - default: - return state; - } -} diff --git a/client/src/hooks/useStakeholders/useStakeholders.js b/client/src/hooks/useStakeholders/useStakeholders.js deleted file mode 100644 index aca1308b8..000000000 --- a/client/src/hooks/useStakeholders/useStakeholders.js +++ /dev/null @@ -1,145 +0,0 @@ -import { useReducer, useEffect } from "react"; -import * as stakeholderService from "../../services/stakeholder-service"; -import { useCategories } from "../useCategories/useCategories"; -import { actionTypes } from "./actionTypes"; -import { reducer } from "./reducer"; -import { initialState } from "./initialState"; -import queryString from "query-string"; - -export function useStakeholders(history, userCoordinates) { - const { data: categories } = useCategories(); - const [state, dispatch] = useReducer(reducer, initialState); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const search = async ( - searchString, - latitude, - longitude, - selectedLocationName, - selectedCategories, - selectedDistance - ) => { - const { - FETCH_FAILURE, - FETCH_REQUEST, - FETCH_SUCCESS, - } = actionTypes.STAKEHOLDERS; - if (!selectedCategories) return; - try { - dispatch({ type: FETCH_REQUEST }); - const stakeholders = await stakeholderService.search({ - name: searchString, - categoryIds: selectedCategories.map((category) => category.id), - latitude, - longitude, - distance: selectedDistance, - }); - dispatch({ - type: FETCH_SUCCESS, - stakeholders, - payload: { - searchString, - selectedLatitude: latitude, - selectedLongitude: longitude, - selectedLocationName, - selectedCategories, - selectedDistance, - }, - }); - history.push( - `/stakeholders?name=${searchString}` + - `&radius=${selectedDistance}` + - `&lat=${latitude}` + - `&lon=${longitude}` + - `&placeName=${selectedLocationName}` + - `&categoryIds=${selectedCategories.map((c) => c.id).join(",")}` - ); - } catch (err) { - console.log(err); - dispatch({ type: FETCH_FAILURE }); - } - }; - - const applyQueryStringParameters = (history, initialState) => { - // The goal here is to overwrite the initialState with - // search criteria from the query string parameters, if - // supplied. The effect should only run once when the - // this hook loads, after the list of categories has loaded. - let { - searchString, - selectedLatitude, - selectedLongitude, - selectedLocationName, - selectedDistance, - selectedCategoryIds, - } = initialState; - - const params = queryString.parse(history.location.search); - - // override initial search parameters with any - // query string parameters - searchString = params.name || searchString; - selectedDistance = params.radius || selectedDistance; - selectedLatitude = Number.parseFloat(params.lat) || selectedLatitude; - selectedLongitude = Number.parseFloat(params.lon) || selectedLongitude; - selectedLocationName = params.placeName ? decodeURI(params.placeName) : ""; - if (params.categoryIds) { - selectedCategoryIds = params.categoryIds.split(","); - } - - dispatch({ - type: actionTypes.INITIALIZE_STATE, - payload: { - searchString, - selectedLatitude: selectedLatitude, - selectedLongitude: selectedLongitude, - selectedLocationName, - selectedCategoryIds, - selectedDistance, - queryParametersLoaded: true, - }, - }); - }; - - // useEffect(() => { - - // fetchCategories(); - - // }, []); - - useEffect(() => { - applyQueryStringParameters(history, initialState); - }, [history]); - - useEffect(() => { - // if we don't have the categories fetched yet, bail - if (!categories) return; - - // If the query string parameters have not been applied, bail - if (!state.queryParametersLoaded) return; - - let { - searchString, - selectedLatitude, - selectedLongitude, - selectedLocationName, - selectedDistance, - selectedCategoryIds, - } = state; - - let selectedCategories = selectedCategoryIds.map( - (id) => categories.filter((cat) => cat.id === Number(id))[0] - ); - - search( - searchString, - selectedLatitude, - selectedLongitude, - selectedLocationName, - selectedCategories, - selectedDistance - ); - }, [categories, state.queryParametersLoaded, state, search]); - - return { state: { ...state, categories }, dispatch, actionTypes, search }; -} diff --git a/client/src/images/mealIcon.js b/client/src/images/mealIcon.js index d00a839b2..c20ad9fa2 100644 --- a/client/src/images/mealIcon.js +++ b/client/src/images/mealIcon.js @@ -1,10 +1,10 @@ import React from "react"; -import { mealProgram, closed } from "../theme/colors"; +import { mealProgram, closed } from "theme/colors"; -const mealIcon = (isClosed) => ( +const MealIcon = ({ isClosed, height = "72px", width = "72px" }) => ( ( ); -export default mealIcon; +export default MealIcon; diff --git a/client/src/images/pantryIcon.js b/client/src/images/pantryIcon.js index a2f65e263..abb70bfe5 100644 --- a/client/src/images/pantryIcon.js +++ b/client/src/images/pantryIcon.js @@ -1,10 +1,10 @@ import React from "react"; -import { foodPantry, closed } from "../theme/colors"; +import { foodPantry, closed } from "theme/colors"; -const pantryIcon = (isClosed) => ( +const PantryIcon = ({ isClosed, height = "72px", width = "72px" }) => ( ( ); -export default pantryIcon; +export default PantryIcon; diff --git a/client/src/images/splitPantryMealIcon.js b/client/src/images/splitPantryMealIcon.js index 27ee74332..552a2f305 100644 --- a/client/src/images/splitPantryMealIcon.js +++ b/client/src/images/splitPantryMealIcon.js @@ -1,14 +1,14 @@ import React from "react"; -import { foodPantry, mealProgram, closed } from "../theme/colors"; +import { foodPantry, mealProgram, closed } from "theme/colors"; -const splitPantryMealIcon = (isClosed) => ( +const SplitPantryMealIcon = ({ isClosed, height = "72px", width = "72px" }) => ( ( ); -export default splitPantryMealIcon; +export default SplitPantryMealIcon; diff --git a/client/src/secrets.js b/client/src/secrets.js index 553e16322..48a43d98c 100644 --- a/client/src/secrets.js +++ b/client/src/secrets.js @@ -1,2 +1 @@ -export const MAPBOX_TOKEN = - "pk.eyJ1IjoibHVjYXNob21lciIsImEiOiJjazFqcnRjcm0wNmZ1M2JwZXg2eDFzMXd3In0.yYpkKLrFCxF-qyBfZH1a8w"; +export const MAPBOX_TOKEN = process.env.REACT_APP_MAPBOX_TOKEN; diff --git a/client/src/services/stakeholder-best-service.js b/client/src/services/stakeholder-best-service.js new file mode 100644 index 000000000..85a7d590a --- /dev/null +++ b/client/src/services/stakeholder-best-service.js @@ -0,0 +1,61 @@ +import axios from "axios"; +import moment from "moment"; +import { getTenantId } from "helpers/Configuration"; + +const baseUrl = "/api/stakeholderbests"; + +const toLocalMoment = (ts) => { + return !ts ? null : moment.utc(ts).local(); +}; + +/* + searchParams is an object with any/all of the following properties: + name - look for this string as substring of the stakeholder's name + categoryIds - array of integers corresponding to the desired categories + latitude + longitude + distance - radius around latitude and longitude + isAssigned - ("yes", "no", "either") + isSubmitted - ("yes", "no", "either") + isApproved - ("yes", "no", "either") + isClaimed - ("yes", "no", "either") + assignedLoginId + claimedLoginId +*/ +export const search = async (searchParams) => { + searchParams = searchParams || {}; + const response = await axios.get(baseUrl, { + params: { + ...searchParams, + tenantId: getTenantId(), + }, + }); + let stakeholders = response.data.map((s) => { + return { + ...s, + createdDate: toLocalMoment(s.createdDate), + modifiedDate: toLocalMoment(s.modifiedDate), + assignedDate: toLocalMoment(s.assignedDate), + submittedDate: toLocalMoment(s.submittedDate), + approvedDate: toLocalMoment(s.approvedDate), + claimedDate: toLocalMoment(s.claimedDate), + }; + }); + + // console.log("stakeholders", stakeholders); + return stakeholders; +}; + +export const getById = async (id) => { + const response = await axios.get(`${baseUrl}/${id}`); + const s = response.data; + return { + ...s, + createdDate: toLocalMoment(s.createdDate), + modifiedDate: toLocalMoment(s.modifiedDate), + assignedDate: toLocalMoment(s.assignedDate), + submittedDate: toLocalMoment(s.submittedDate), + approvedDate: toLocalMoment(s.approvedDate), + claimedDate: toLocalMoment(s.claimedDate), + }; +}; diff --git a/client/src/services/stakeholder-service.js b/client/src/services/stakeholder-service.js index 7b95e6a00..4b48717de 100644 --- a/client/src/services/stakeholder-service.js +++ b/client/src/services/stakeholder-service.js @@ -9,47 +9,9 @@ const toLocalMoment = (ts) => { return !ts ? null : moment.utc(ts).local(); }; -/* - searchParams is an object with any/all of the following properties: - name - look for this string as substring of the stakeholder's name - categoryIds - array of integers corresponding to the desired categories - latitude - longitude - distance - radius around latitude and longitude - isAssigned - ("yes", "no", "either") - isSubmitted - ("yes", "no", "either") - isApproved - ("yes", "no", "either") - isClaimed - ("yes", "no", "either") - assignedLoginId - claimedLoginId -*/ export const search = async (searchParams) => { searchParams = searchParams || {}; - const response = await axios.get(baseUrl, { - params: { - ...searchParams, - tenantId: getTenantId(), - }, - }); - let stakeholders = response.data.map((s) => { - return { - ...s, - createdDate: toLocalMoment(s.createdDate), - modifiedDate: toLocalMoment(s.modifiedDate), - assignedDate: toLocalMoment(s.assignedDate), - submittedDate: toLocalMoment(s.submittedDate), - approvedDate: toLocalMoment(s.approvedDate), - claimedDate: toLocalMoment(s.claimedDate), - }; - }); - - // console.log("stakeholders", stakeholders); - return stakeholders; -}; - -export const searchDashboard = async (searchParams) => { - searchParams = searchParams || {}; - const response = await axios.get(`${baseUrl}/dashboard`, { + const response = await axios.get(`${baseUrl}`, { params: searchParams, }); let stakeholders = response.data.map((s) => { diff --git a/package.json b/package.json index 9f6573149..4f77b5818 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foodoasis", - "version": "1.0.21", + "version": "1.0.22", "author": "Hack for LA", "description": "Web API Server for Food Oasis", "main": "server.js",