Compare commits

...

29 Commits

Author SHA1 Message Date
Naida Vatric
ef5de27c06 Add currency to price filters - added above input 2019-12-18 22:21:56 +01:00
Naida Vatric
bee390aa15 RealEstate included even is price is null. 2019-12-18 21:49:12 +01:00
Naida Vatric
251437f815 Changed searchRequest to include case of incomplete ads wanted. 2019-12-18 02:04:31 +01:00
Naida Vatric
4391aa5939 First review changes: applied prettier, ternary and changed accesRoadType filter 2019-12-17 11:28:00 +01:00
Naida Vatric
c672b3ab9f First review changes: applied prettier, ternary and changed accesRoadType filter 2019-12-17 11:18:58 +01:00
Bilal Catic
76f4ed0a30 apply prettier 2019-12-16 22:04:37 +01:00
Bilal Catic
73b3f0d22f Merge branch 'master' into 'add-even-more-filters'
# Conflicts:
#   app/config/appConfig.js
2019-12-16 20:52:40 +00:00
Bilal Catic
547411f189 Merge branch 'map-key-env-var' into 'master'
Map key env var

See merge request saburly/marketalarm/web!76
2019-12-16 20:49:58 +00:00
Bilal Catic
a45a0ec361 apply prettier 2019-12-16 21:40:21 +01:00
Naida Vatric
43074b6eb3 Finished map key to env 2019-12-16 21:40:21 +01:00
Naida Vatric
cb52c8592a Moved API Google Map key to env variables. 2019-12-16 21:40:21 +01:00
Naida Vatric
5a2fdb7291 Queries for db search changed. Needs testing. 2019-12-14 01:10:48 +01:00
Naida Vatric
e83712fb33 Changed acces road type check and include incomplete 2019-12-13 00:45:28 +01:00
Naida Vatric
0e585e74ae Merge branch 'add-even-more-filters' of gitlab.com:saburly/marketalarm/web into add-even-more-filters 2019-12-11 22:52:26 +01:00
Naida Vatric
e6e1688a49 Changed searchRequest helper 2019-12-11 22:44:26 +01:00
Naida Vatric
dee7c6000a WiP - changed db helpers 2019-12-11 01:24:18 +01:00
Naida Vatric
fbcda328b7 Merge branch 'update-dockerfile-readme-setup' into 'master'
Update docker file, readme and setup script

See merge request saburly/marketalarm/web!75
2019-12-10 20:44:53 +00:00
Naida Vatric
6f729b4135 "Taking over work in progress." 2019-12-10 11:07:31 +01:00
Naida Vatric
ef4fff4e70 Finished map key to env 2019-12-08 22:16:31 +01:00
Naida Vatric
ade28eb981 Moved API Google Map key to env variables. 2019-12-08 00:58:06 +01:00
Bilal Catic
f8ea2f0f78 include new fields for search request 2019-11-18 22:46:18 +01:00
Bilal Catic
232221af9e improve css 2019-11-18 22:46:18 +01:00
Bilal Catic
271af35f0c add new enum value for access road type and heating type; add filter enums 2019-11-18 22:46:18 +01:00
Bilal Catic
6baa151ea2 add new fields to the search request table and model 2019-11-18 22:46:18 +01:00
Bilal Catic
e42531ff57 specify and use custom css class for checkbox labels 2019-11-18 22:46:18 +01:00
Bilal Catic
002a8e8572 add more filters to different tab on filters page; update css 2019-11-18 22:46:18 +01:00
Bilal Catic
fd8592c581 modify materialize tabs style to match Kivi color scheme 2019-11-18 22:46:18 +01:00
Bilal Catic
5cab9ee7c4 remove accordion files and import 2019-11-18 22:44:26 +01:00
Bilal Catic
1106f92560 add accordion for additional filters 2019-11-18 22:44:26 +01:00
18 changed files with 1019 additions and 189 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
node_modules/
.env
.idea/
.eslintrc
.vscode/

View File

@@ -104,6 +104,13 @@ const AD_CATEGORY = {
id: "FLAT",
title: "Stan",
hasGardenSize: false,
hasAccesRoadType: true,
hasBalconyProp: true,
hasNewBuildingProp: true,
hasElevatorProp: true,
hasNumberOfRoom: true,
hasNumberOfFloors: false,
hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -112,6 +119,13 @@ const AD_CATEGORY = {
id: "HOUSE",
title: "Kuća",
hasGardenSize: true,
hasAccesRoadType: true,
hasBalconyProp: true,
hasNewBuildingProp: true,
hasElevatorProp: false,
hasNumberOfRoom: true,
hasNumberOfFloors: true,
hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS,
@@ -121,6 +135,13 @@ const AD_CATEGORY = {
id: "OFFICE",
title: "Kancelarija",
hasGardenSize: false,
hasAccesRoadType: true,
hasBalconyProp: false,
hasNewBuildingProp: true,
hasElevatorProp: true,
hasNumberOfRoom: true,
hasNumberOfFloors: false,
hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -129,6 +150,13 @@ const AD_CATEGORY = {
id: "LAND",
title: "Zemljište",
hasGardenSize: false,
hasAccesRoadType: true,
hasBalconyProp: false,
hasNewBuildingProp: false,
hasElevatorProp: false,
hasNumberOfRoom: false,
hasNumberOfFloors: false,
hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: LAND_SIZE_SLIDER_OPTIONS
@@ -137,6 +165,13 @@ const AD_CATEGORY = {
id: "APARTMENT",
title: "Apartman",
hasGardenSize: false,
hasAccesRoadType: true,
hasBalconyProp: true,
hasNewBuildingProp: true,
hasElevatorProp: true,
hasNumberOfRoom: true,
hasNumberOfFloors: false,
hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -145,6 +180,13 @@ const AD_CATEGORY = {
id: "GARAGE",
title: "Garaža",
hasGardenSize: false,
hasAccesRoadType: true,
hasBalconyProp: false,
hasNewBuildingProp: false,
hasElevatorProp: false,
hasNumberOfRoom: false,
hasNumberOfFloors: false,
hasFloorProp: false,
priceSliderOptionsSale: GARAGE_PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: GARAGE_PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: GARAGE_SIZE_SLIDER_OPTIONS
@@ -153,6 +195,13 @@ const AD_CATEGORY = {
id: "COTTAGE",
title: "Vikendica",
hasGardenSize: true,
hasAccesRoadType: true,
hasBalconyProp: true,
hasNewBuildingProp: true,
hasElevatorProp: false,
hasNumberOfRoom: true,
hasNumberOfFloors: true,
hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS,
@@ -199,6 +248,10 @@ const EMAIL_FREQUENCY = {
};
const HEATING_TYPE = {
ANY: {
id: "ANY",
title: "Svi"
},
NO_HEATING: {
id: "NO_HEATING",
title: "Nije uvedeno"
@@ -238,6 +291,10 @@ const HEATING_TYPE = {
};
const ACCESS_ROAD_TYPE = {
ANY: {
id: "ANY",
title: "Svi"
},
ASPHALT: {
id: "ASPHALT",
title: "Asfalt"

110
app/common/filterEnums.js Normal file
View File

@@ -0,0 +1,110 @@
const { AD_CATEGORY, ACCESS_ROAD_TYPE, HEATING_TYPE } = require("./enums");
const ADVANCED_BOOLEAN_FILTERS = [
{
dbField: "balcony",
title: "Balkon",
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.HOUSE,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.COTTAGE
]
},
{
dbField: "elevator",
title: "Lift",
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.OFFICE
]
},
{
dbField: "newBuilding",
title: "Novogradnja",
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.HOUSE,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.COTTAGE,
AD_CATEGORY.OFFICE
]
}
];
const ADVANCED_SEGMENT_SELECT_FILTERS = [
{
dbField: "accessRoadType",
title: "Pristupni put",
values: Object.keys(ACCESS_ROAD_TYPE).map(key => ACCESS_ROAD_TYPE[key]),
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.HOUSE,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.COTTAGE,
AD_CATEGORY.OFFICE,
AD_CATEGORY.LAND,
AD_CATEGORY.GARAGE
]
}
// {
// dbField: "heatingType",
// title: "Vrsta grijanja",
// values: Object.keys(HEATING_TYPE).map(key => HEATING_TYPE[key]),
// categoriesToShow: [
// AD_CATEGORY.FLAT,
// AD_CATEGORY.HOUSE,
// AD_CATEGORY.APARTMENT,
// AD_CATEGORY.COTTAGE,
// AD_CATEGORY.OFFICE
// ]
// }
];
const ADVANCED_RANGE_FILTERS = [
{
id: "numberOfFloors",
title: "Broj spratova",
dbFieldMin: "numberOfFloorsMin",
dbFieldMax: "numberOfFloorsMax",
validValueMin: -1,
validValueMax: 50,
categoriesToShow: [AD_CATEGORY.HOUSE, AD_CATEGORY.COTTAGE]
},
{
id: "floor",
title: "Sprat",
dbFieldMin: "floorMin",
dbFieldMax: "floorMax",
validValueMin: -10,
validValueMax: 50,
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.OFFICE
]
},
{
id: "numberOfRooms",
title: "Broj soba",
dbFieldMin: "numberOfRoomsMin",
dbFieldMax: "numberOfRoomsMax",
decimalPlaces: 1,
validValueMin: 0,
validValueMax: 200,
categoriesToShow: [
AD_CATEGORY.FLAT,
AD_CATEGORY.HOUSE,
AD_CATEGORY.APARTMENT,
AD_CATEGORY.COTTAGE,
AD_CATEGORY.OFFICE
]
}
];
module.exports = {
ADVANCED_BOOLEAN_FILTERS,
ADVANCED_SEGMENT_SELECT_FILTERS,
ADVANCED_RANGE_FILTERS
};

View File

@@ -30,6 +30,8 @@ const MAX_REAL_ESTATES_IN_FIRST_EMAIL =
const PRINT_CRAWLER_DEBUG = process.env.PRINT_CRAWLER_DEBUG_INFO || 0;
const API_MAP_KEY = process.env.API_MAP_KEY || "";
module.exports = {
APP_PORT,
APP_URL,
@@ -39,5 +41,6 @@ module.exports = {
AWS_EMAIL_CONFIG,
MAX_REAL_ESTATES_IN_EMAIL,
MAX_REAL_ESTATES_IN_FIRST_EMAIL,
PRINT_CRAWLER_DEBUG
PRINT_CRAWLER_DEBUG,
API_MAP_KEY
};

View File

@@ -1,5 +1,10 @@
const { currentSearchRequest } = require("../helpers/url");
const { AD_CATEGORY, AD_TYPE } = require("../common/enums");
const { AD_CATEGORY, AD_TYPE, ACCESS_ROAD_TYPE } = require("../common/enums");
const {
ADVANCED_BOOLEAN_FILTERS,
ADVANCED_SEGMENT_SELECT_FILTERS,
ADVANCED_RANGE_FILTERS
} = require("../common/filterEnums");
const getFilters = async (req, res) => {
const searchRequest = await currentSearchRequest(req);
@@ -19,7 +24,18 @@ const getFilters = async (req, res) => {
sizeMin,
sizeMax,
gardenSizeMin,
gardenSizeMax
gardenSizeMax,
numberOfRoomsMin,
numberOfRoomsMax,
numberOfFloorsMin,
numberOfFloorsMax,
floorMin,
floorMax,
includeIncompleteAds,
balcony,
elevator,
newBuilding,
accessRoadType
} = searchRequest;
const category = AD_CATEGORY[realEstateType] || AD_CATEGORY.FLAT;
@@ -41,6 +57,40 @@ const getFilters = async (req, res) => {
return;
}
// TODO: Maybe this is slow, pay attention to this
const filterFilters = filterObject => {
const filterCategories = filterObject.categoriesToShow;
return filterCategories.indexOf(category) !== -1;
};
const advancedBooleanFilterObjects = ADVANCED_BOOLEAN_FILTERS.filter(
filterFilters
);
const advancedSegmentSelectFilterObjects = ADVANCED_SEGMENT_SELECT_FILTERS.filter(
filterFilters
);
const advancedRangeFilterObjects = ADVANCED_RANGE_FILTERS.filter(
filterFilters
);
const advancedBooleanFilterValues = {
includeIncompleteAds,
balcony,
elevator,
newBuilding
};
const advancedSegmentSelectFilterValues = {
accessRoadType
};
const advancedRangeFilterValues = {
numberOfFloorsMin,
numberOfFloorsMax,
numberOfRoomsMin,
numberOfRoomsMax,
floorMin,
floorMax
};
if (priceMin || priceMax) {
priceSliderOptions.start = [priceMin, priceMax];
}
@@ -58,7 +108,14 @@ const getFilters = async (req, res) => {
hasGardenSize,
priceSliderOptions: JSON.stringify(priceSliderOptions),
sizeSliderOptions: JSON.stringify(sizeSliderOptions),
gardenSizeSliderOptions: JSON.stringify(gardenSizeSliderOptions)
gardenSizeSliderOptions: JSON.stringify(gardenSizeSliderOptions),
advancedBooleanFilterObjects,
advancedBooleanFilterValues,
advancedSegmentSelectFilterObjects,
advancedSegmentSelectFilterValues,
advancedRangeFilterObjects,
advancedRangeFilterValues,
includeIncompleteAds
});
};
@@ -78,13 +135,91 @@ const postFilters = async (req, res) => {
const sizeMin = parseInt(req.body.sizeMin) || 0;
const sizeMax = parseInt(req.body.sizeMax) || 0;
//TODO: Filter validation
const advancedRangeFilters = {};
ADVANCED_RANGE_FILTERS.forEach(filter => {
let parsingFunction = parseInt;
if (filter.decimalPlaces) {
parsingFunction = parseFloat;
}
advancedRangeFilters[filter.dbFieldMin] = parsingFunction(
req.body[filter.dbFieldMin]
);
advancedRangeFilters[filter.dbFieldMax] = parsingFunction(
req.body[filter.dbFieldMax]
);
advancedRangeFilters[filter.dbFieldMin] = isNaN(
advancedRangeFilters[filter.dbFieldMin]
)
? null
: advancedRangeFilters[filter.dbFieldMin];
advancedRangeFilters[filter.dbFieldMax] = isNaN(
advancedRangeFilters[filter.dbFieldMax]
)
? null
: advancedRangeFilters[filter.dbFieldMax];
try {
if (filter.decimalPlaces) {
advancedRangeFilters[filter.dbFieldMin] = advancedRangeFilters[
filter.dbFieldMin
].toFixed(filter.decimalPlaces);
advancedRangeFilters[filter.dbFieldMax] = advancedRangeFilters[
filter.dbFieldMax
].toFixed(filter.decimalPlaces);
}
} catch (e) {
advancedRangeFilters[filter.dbFieldMin] = null;
advancedRangeFilters[filter.dbFieldMax] = null;
}
if (
advancedRangeFilters[filter.dbFieldMin] < filter.validValueMin ||
advancedRangeFilters[filter.dbFieldMin] > filter.validValueMax
) {
advancedRangeFilters[filter.dbFieldMin] = filter.validValueMin;
}
if (
advancedRangeFilters[filter.dbFieldMax] < filter.validValueMin ||
advancedRangeFilters[filter.dbFieldMax] > filter.validValueMax
) {
advancedRangeFilters[filter.dbFieldMax] = filter.validValueMax;
}
});
const includeIncompleteAds = req.body.includeIncompleteAds === "on";
const balcony = req.body.balcony === "on";
const elevator = req.body.elevator === "on";
const newBuilding = req.body.newBuilding === "on";
const accessRoadType = req.body.accessRoadType;
if (!ACCESS_ROAD_TYPE[accessRoadType]) {
res.render("notFound", { title: " Greška !" });
return;
}
//TODO: Filter validation
searchRequest.priceMin = priceMin;
searchRequest.priceMax = priceMax;
searchRequest.sizeMin = sizeMin;
searchRequest.sizeMax = sizeMax;
for (const filter of Object.keys(advancedRangeFilters)) {
searchRequest[filter] = advancedRangeFilters[filter];
}
searchRequest.balcony = balcony;
searchRequest.elevator = elevator;
searchRequest.newBuilding = newBuilding;
searchRequest.includeIncompleteAds = includeIncompleteAds;
searchRequest.accessRoadType = accessRoadType;
if (
req.body.gardenSizeMin !== undefined &&
req.body.gardenSizeMax !== undefined
@@ -97,7 +232,6 @@ const postFilters = async (req, res) => {
searchRequest.gardenSizeMin = gardenSizeMin;
searchRequest.gardenSizeMax = gardenSizeMax;
}
await searchRequest.save();
res.redirect(nextStepUrl);

View File

@@ -2,7 +2,6 @@
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
const bulkUpsertRealEstates = async realEstateData => {
try {
const fieldsToUpdateIfDuplicate = [
@@ -87,7 +86,20 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
sizeMax,
adType,
realEstateType,
areaToSearch
areaToSearch,
gardenSizeMin,
gardenSizeMax,
numberOfRoomsMin,
numberOfRoomsMax,
numberOfFloorsMin,
numberOfFloorsMax,
floorMin,
floorMax,
includeIncompleteAds,
balcony,
elevator,
newBuilding,
accessRoadType
} = searchRequest;
const longitudeColumn = sequelize.col("locationLong");
@@ -116,12 +128,20 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
const geoSearchQueryPart = sequelize.where(contains, true);
//General queries contain only attributes that are defined for every searchreq
//Query for case of complete ads
const query = {
adType,
realEstateType,
price: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
},
area: {
[Op.lte]: sizeMax,
@@ -130,10 +150,148 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
[Op.and]: geoSearchQueryPart
};
//Query for case of incomplete ads
const queryIncludeIncomplete = {
adType,
realEstateType,
price: {
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
},
area: {
[Op.or]: {
[Op.and]: {
[Op.lte]: sizeMax,
[Op.gte]: sizeMin
},
[Op.is]: null
}
},
[Op.and]: geoSearchQueryPart
};
//Every other attribute is checked separately and included in query only if it is defined
if (gardenSizeMax && gardenSizeMin) {
query.gardenSize = {
[Op.lte]: gardenSizeMax,
[Op.gte]: gardenSizeMin
};
queryIncludeIncomplete.gardenSize = {
[Op.or]: {
[Op.and]: {
[Op.lte]: gardenSizeMax,
[Op.gte]: gardenSizeMin
},
[Op.is]: null
}
};
}
if (numberOfRoomsMin && numberOfRoomsMax) {
query.numberOfRooms = {
[Op.lte]: numberOfRoomsMax,
[Op.gte]: numberOfRoomsMin
};
queryIncludeIncomplete.numberOfRooms = {
[Op.or]: {
[Op.and]: {
[Op.lte]: numberOfRoomsMax,
[Op.gte]: numberOfRoomsMin
},
[Op.is]: null
}
};
}
if (numberOfFloorsMin && numberOfFloorsMax) {
query.numberOfFloors = {
[Op.lte]: numberOfFloorsMax,
[Op.gte]: numberOfFloorsMin
};
queryIncludeIncomplete.numberOfFloors = {
[Op.or]: {
[Op.and]: {
[Op.lte]: numberOfFloorsMax,
[Op.gte]: numberOfFloorsMin
},
[Op.is]: null
}
};
}
if (floorMin && floorMax) {
query.floor = {
[Op.lte]: floorMax,
[Op.gte]: floorMin
};
queryIncludeIncomplete.floor = {
[Op.or]: {
[Op.and]: {
[Op.lte]: floorMax,
[Op.gte]: floorMin
},
[Op.is]: null
}
};
}
if (balcony) {
query.balcony = {
[Op.eq]: balcony
};
queryIncludeIncomplete.balcony = {
[Op.or]: {
[Op.eq]: balcony,
[Op.is]: null
}
};
}
if (newBuilding) {
query.newBuilding = {
[Op.eq]: newBuilding
};
queryIncludeIncomplete.newBuilding = {
[Op.or]: {
[Op.eq]: newBuilding,
[Op.is]: null
}
};
}
if (elevator) {
query.elevator = {
[Op.eq]: elevator
};
queryIncludeIncomplete.elevator = {
[Op.or]: {
[Op.eq]: elevator,
[Op.is]: null
}
};
}
if (accessRoadType !== "ANY") {
query.accessRoadType = {
[Op.eq]: accessRoadType
};
queryIncludeIncomplete.accessRoadType = {
[Op.or]: {
[Op.eq]: accessRoadType,
[Op.is]: null
}
};
}
const order = [["updatedAt", "desc"]];
return await db.RealEstate.findAll({
where: query,
return db.RealEstate.findAll({
where: includeIncompleteAds ? queryIncludeIncomplete : query,
limit: maxResults,
order
});

View File

@@ -2,11 +2,13 @@
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
const { AD_CATEGORY } = require("../../common/enums");
const getSearchRequest = async searchRequestId => {
try {
return await db.SearchRequest.findByPk(searchRequestId);
} catch (error) {
console.log("searchrequest.js", error);
return null;
}
};
@@ -22,7 +24,15 @@ const findSearchRequestsForRealEstate = async realEstate => {
adType,
realEstateType,
locationLat,
locationLong
locationLong,
accessRoadType,
balcony,
newBuilding,
elevator,
gardenSize,
numberOfRooms,
numberOfFloors,
floor
} = realEstate;
if (!locationLat || !locationLong) {
@@ -39,12 +49,20 @@ const findSearchRequestsForRealEstate = async realEstate => {
const geoSearchQueryPart = sequelize.where(contains, true);
//General query contains only attributes that are defined for every RealEstate - not null
const query = {
adType,
realEstateType,
subscribed: true,
[Op.and]: geoSearchQueryPart
};
//Needed for defining which attribute should exist or not
const realEstateTypeObject = AD_CATEGORY[realEstateType];
//Needed to decide on including incomplete RealEstates data
let checkForIncompleteWanted = false;
//Attributes are checked separately and included in query only if defined
//Price and area should be defined for every property
if (price) {
query.priceMin = {
@@ -62,8 +80,96 @@ const findSearchRequestsForRealEstate = async realEstate => {
query.sizeMax = {
[Op.gte]: area
};
} else {
checkForIncompleteWanted = true;
}
//Other attributes can be defined or not depending on RealEstate type
if (gardenSize) {
query.gardenSizeMin = {
[Op.lte]: gardenSize
};
query.gardenSizeMax = {
[Op.gte]: gardenSize
};
} else if (realEstateTypeObject.hasGardenSize) {
checkForIncompleteWanted = true;
}
if (numberOfRooms) {
query.numberOfRoomsMin = {
[Op.lte]: numberOfRooms
};
query.numberOfRoomsMax = {
[Op.gte]: numberOfRooms
};
} else if (realEstateTypeObject.hasNumberOfRoom) {
checkForIncompleteWanted = true;
}
if (numberOfFloors) {
query.numberOfFloorsMin = {
[Op.lte]: numberOfFloors
};
query.numberOfFloorsMax = {
[Op.gte]: numberOfFloors
};
} else if (realEstateTypeObject.hasNumberOfFloors) {
checkForIncompleteWanted = true;
}
if (floor) {
query.floorMin = {
[Op.lte]: floor
};
query.floorMax = {
[Op.gte]: floor
};
} else if (realEstateTypeObject.hasFloorProp) {
checkForIncompleteWanted = true;
}
if (accessRoadType) {
query.accessRoadType = {
[Op.or]: {
[Op.eq]: "ANY",
[Op.eq]: accessRoadType
}
};
} else if (realEstateTypeObject.hasAccesRoadType) {
checkForIncompleteWanted = true;
}
if (balcony) {
query.balcony = {
[Op.eq]: balcony
};
} else if (realEstateTypeObject.hasBalconyProp) {
checkForIncompleteWanted = true;
}
if (newBuilding) {
query.newBuilding = {
[Op.eq]: newBuilding
};
} else if (realEstateTypeObject.hasNewBuildingProp) {
checkForIncompleteWanted = true;
}
if (elevator) {
query.elevator = {
[Op.eq]: elevator
};
} else if (realEstateTypeObject.hasElevatorProp) {
checkForIncompleteWanted = true;
}
//If one of the attributes that exists for property type is null
//we include in query to check if incomplete real estates are accepted
if (checkForIncompleteWanted) {
query.includeIncompleteAds = {
[Op.eq]: true
};
}
return await db.SearchRequest.findAll({ where: query });
};

View File

@@ -0,0 +1,64 @@
"use strict";
const { ACCESS_ROAD_TYPE, HEATING_TYPE } = require("../common/enums");
module.exports = {
up: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.addColumn("SearchRequests", "includeIncompleteAds", {
type: Sequelize.BOOLEAN
}),
queryInterface.addColumn("SearchRequests", "balcony", {
type: Sequelize.BOOLEAN
}),
queryInterface.addColumn("SearchRequests", "newBuilding", {
type: Sequelize.BOOLEAN
}),
queryInterface.addColumn("SearchRequests", "elevator", {
type: Sequelize.BOOLEAN
}),
queryInterface.addColumn("SearchRequests", "numberOfRoomsMin", {
type: Sequelize.REAL
}),
queryInterface.addColumn("SearchRequests", "numberOfRoomsMax", {
type: Sequelize.REAL
}),
queryInterface.addColumn("SearchRequests", "numberOfFloorsMin", {
type: Sequelize.INTEGER
}),
queryInterface.addColumn("SearchRequests", "numberOfFloorsMax", {
type: Sequelize.INTEGER
}),
queryInterface.addColumn("SearchRequests", "floorMin", {
type: Sequelize.INTEGER
}),
queryInterface.addColumn("SearchRequests", "floorMax", {
type: Sequelize.INTEGER
}),
queryInterface.addColumn("SearchRequests", "accessRoadType", {
type: Sequelize.TEXT,
defaultValue: ACCESS_ROAD_TYPE.ANY.id
}),
queryInterface.addColumn("SearchRequests", "heatingType", {
type: Sequelize.TEXT,
defaultValue: HEATING_TYPE.ANY.id
})
]);
},
down: (queryInterface, Sequelize) => {
return Promise.all([
queryInterface.removeColumn("SearchRequests", "includeIncompleteAds"),
queryInterface.removeColumn("SearchRequests", "balcony"),
queryInterface.removeColumn("SearchRequests", "newBuilding"),
queryInterface.removeColumn("SearchRequests", "elevator"),
queryInterface.removeColumn("SearchRequests", "numberOfRoomsMin"),
queryInterface.removeColumn("SearchRequests", "numberOfRoomsMax"),
queryInterface.removeColumn("SearchRequests", "numberOfFloorsMin"),
queryInterface.removeColumn("SearchRequests", "numberOfFloorsMax"),
queryInterface.removeColumn("SearchRequests", "floorMin"),
queryInterface.removeColumn("SearchRequests", "floorMax"),
queryInterface.removeColumn("SearchRequests", "accessRoadType"),
queryInterface.removeColumn("SearchRequests", "heatingType")
]);
}
};

View File

@@ -69,7 +69,19 @@ module.exports = (sequelize, DataTypes) => {
},
deletedEmail: {
type: DataTypes.TEXT
}
},
includeIncompleteAds: DataTypes.BOOLEAN,
balcony: DataTypes.BOOLEAN,
elevator: DataTypes.BOOLEAN,
newBuilding: DataTypes.BOOLEAN,
numberOfRoomsMin: DataTypes.REAL,
numberOfRoomsMax: DataTypes.REAL,
numberOfFloorsMin: DataTypes.INTEGER,
numberOfFloorsMax: DataTypes.INTEGER,
floorMin: DataTypes.INTEGER,
floorMax: DataTypes.INTEGER,
accessRoadType: DataTypes.TEXT,
heatingType: DataTypes.TEXT
});
return SearchRequest;

View File

@@ -110,3 +110,47 @@ h3 {
.collection a.collection-item:not(.active):hover {
background-color: rgba(2, 173, 186, 0.2);
}
.tabs .tab a {
color: #02adba;
-webkit-transition: color 0.28s ease, background-color 0.28s ease;
transition: color 0.28s ease, background-color 0.28s ease;
}
.tabs .tab a:focus,
.tabs .tab a:focus.active {
background-color: rgba(2, 173, 186, 0.2);
}
.tabs .tab a:hover,
.tabs .tab a.active {
color: #02adba;
}
.tabs .indicator {
background-color: #02adba;
}
[type="checkbox"].filled-in:checked + span:not(.lever):after {
border: 2px solid #02adba;
background-color: #02adba;
}
[type="checkbox"].filled-in:not(:checked) + span:not(.lever):after {
background-color: transparent;
border: 2px solid #02adba;
}
.distinguished {
border: 2px solid #02adba;
border-radius: 4px;
padding: 5px 5px 3px 5px;
margin-left: -5px;
}
.checkbox-label {
color: black;
font-size: 14px;
}
.column-label {
position: relative;
margin-top: 2rem;
margin-bottom: 1rem;
}

View File

@@ -25,6 +25,12 @@
border-right: 1px solid #02adba;
}
.segmented.small [type="radio"]:not(:checked) + span,
.segmented.small [type="radio"]:checked + span {
padding-left: 7px;
padding-right: 7px;
}
.segmented :last-child .label {
border-right: none;
}

View File

@@ -0,0 +1,69 @@
<br>
<% for (const filter of advancedBooleanFilterObjects){ %>
<p>
<label class="checkbox-label">
<input type="checkbox" class="filled-in" name="<%= filter.dbField %>"
<% if (advancedBooleanFilterValues[filter.dbField]) { %>
checked
<% } %>>
<span><%= filter.title %></span>
</label>
</p>
<% } %>
<br>
<% for (const filter of advancedRangeFilterObjects){ %>
<div class="row">
<p class="column-label col s5 m3 l2">
<%= filter.title %>
</p>
<div class="input-field col s3 m4 l5">
<input
id="<%= filter.dbFieldMin %>"
name="<%= filter.dbFieldMin %>"
type="number"
value="<%= advancedRangeFilterValues[filter.dbFieldMin] !== undefined ? advancedRangeFilterValues[filter.dbFieldMin] : ""%>"
>
<label for="<%= filter.dbFieldMin %>">Od</label>
</div>
<div class="input-field col s3 m4 l5">
<input
id="<%= filter.dbFieldMax %>"
name="<%= filter.dbFieldMax %>"
type="number"
value="<%= advancedRangeFilterValues[filter.dbFieldMax] !== undefined ? advancedRangeFilterValues[filter.dbFieldMax] : ""%>"
>
<label for="<%= filter.dbFieldMax %>">Do</label>
</div>
</div>
<% } %>
<br>
<% for (const filter of advancedSegmentSelectFilterObjects){ %>
<div>
<label class="checkbox-label"><%= filter.title %>: </label><br><br>
<span class="segmented small">
<% for (const segmentObject of filter.values) { %>
<label>
<input type="radio" name="<%= filter.dbField %>" value="<%= segmentObject.id %>"
<% if (advancedSegmentSelectFilterValues[filter.dbField] === segmentObject.id) { %>
checked
<% } %>>
<span class="label"><%= segmentObject.title %></span>
</label>
<% } %>
</span>
</div>
<% } %>
<br>
<p class="distinguished">
<label class="checkbox-label">
<input type="checkbox" class="filled-in" name="includeIncompleteAds"
<% if (includeIncompleteAds) { %>
checked
<% } %>>
<span>Uključi i oglase bez potpunih informacija</span>
</label>
</p>

View File

@@ -177,5 +177,5 @@
});
});
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAna8ohfV2HBMcxGk_29vqxU5Z_bDickqg&language=bs&libraries=places&callback=initMap" async
<script src="https://maps.googleapis.com/maps/api/js?key=<%= process.env.API_MAP_KEY %>&language=bs&libraries=places&callback=initMap" async
defer></script>

View File

@@ -1,63 +1,20 @@
<br>
<form id="filtersForm" method="POST">
<br>
<div class="row center-align">
<h5>Cijena</h5>
<br><br>
<div class="center-align no-ui-slider" id="priceFilter"></div>
</div>
<br>
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input class="sliderInputBox" type="number" id="priceMin" name="priceMin">
<div class="col s12">
<ul class="tabs">
<li class="tab col s6"><a href="#standardFilters">Filteri</a></li>
<li class="tab col s6"><a href="#advancedFilters">Napredni filteri</a></li>
</ul>
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input class="sliderInputBox" type="number" id="priceMax" name="priceMax">
<div id="standardFilters" class="col s12">
<%- include("./standardFilters.ejs") %>
</div>
<div id="advancedFilters" class="col s12">
<%- include("./advancedFilters.ejs") %>
</div>
</div>
<br>
<div class="row center-align">
<h5>Površina</h5>
<br><br>
<div class="center-align no-ui-slider" id="sizeFilter"></div>
</div>
<br>
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input class="sliderInputBox" type="number" id="sizeMin" name="sizeMin">
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input class="sliderInputBox" type="number" id="sizeMax" name="sizeMax">
</div>
</div>
<br>
<% if(hasGardenSize) { %>
<div class="row center-align">
<h5>Površina okućnice</h5>
<br><br>
<div class="center-align no-ui-slider" id="gardenSizeFilter"></div>
</div>
<br>
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input class="sliderInputBox" type="number" id="gardenSizeMin" name="gardenSizeMin">
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input class="sliderInputBox" type="number" id="gardenSizeMax" name="gardenSizeMax">
</div>
</div>
<% } %>
<div class="row">
<div class="col s6 push-s3">
<a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn">Dalje</a>
@@ -66,125 +23,7 @@
</form>
<script>
$(document).ready(function() {
const priceSliderOptions = <%- priceSliderOptions %>;
const sizeSliderOptions = <%- sizeSliderOptions %>;
const priceStep = priceSliderOptions.step;
const sizeStep = sizeSliderOptions.step;
delete priceSliderOptions.step;
delete sizeSliderOptions.step;
function updatePriceInputs(values, handle, unencoded) {
$("#priceMin").val(Math.round(unencoded[0]/priceStep)*priceStep);
$("#priceMax").val(Math.round(unencoded[1]/priceStep)*priceStep);
}
function updateSizeInputs(values, handle, unencoded) {
$("#sizeMin").val(Math.round(unencoded[0]/sizeStep)*sizeStep);
$("#sizeMax").val(Math.round(unencoded[1]/sizeStep)*sizeStep);
}
const priceSlider = document.getElementById("priceFilter");
const sizeSlider = document.getElementById("sizeFilter");
const priceSliderObject = noUiSlider.create(priceSlider, priceSliderOptions);
const sizeSliderObject = noUiSlider.create(sizeSlider, sizeSliderOptions);
priceSliderObject.on('slide', updatePriceInputs);
sizeSliderObject.on('slide', updateSizeInputs);
function priceMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = priceSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
priceSliderObject.set([fixedNewValue, null]);
$("#priceMin").val(Math.round(priceSliderObject.get()[0]));
}
}
function priceMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
priceSliderObject.set([null, newValue]);
$("#priceMax").val(Math.round(priceSliderObject.get()[1]));
}
}
$("#priceMin").val(priceSliderOptions.start[0]);
$("#priceMax").val(priceSliderOptions.start[1]);
$("#priceMin").change(priceMinChangeHandler);
$("#priceMax").change(priceMaxChangeHandler);
function sizeMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = sizeSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
sizeSliderObject.set([fixedNewValue, null]);
$("#sizeMin").val(Math.round(sizeSliderObject.get()[0]));
}
}
function sizeMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
sizeSliderObject.set([null, newValue]);
$("#sizeMax").val(Math.round(sizeSliderObject.get()[1]));
}
}
$("#sizeMin").val(sizeSliderOptions.start[0]);
$("#sizeMax").val(sizeSliderOptions.start[1]);
$("#sizeMin").change(sizeMinChangeHandler);
$("#sizeMax").change(sizeMaxChangeHandler);
<% if(hasGardenSize) { %>
const gardenSizeSliderOptions = <%- gardenSizeSliderOptions %>;
const gardenSizeStep = gardenSizeSliderOptions.step;
delete gardenSizeSliderOptions.step;
function updateGardenSizeInputs(values, handle, unencoded) {
$("#gardenSizeMin").val(Math.round(unencoded[0]/gardenSizeStep)*gardenSizeStep);
$("#gardenSizeMax").val(Math.round(unencoded[1]/gardenSizeStep)*gardenSizeStep);
}
const gardenSizeSlider = document.getElementById("gardenSizeFilter");
const gardenSizeSliderObject = noUiSlider.create(gardenSizeSlider, gardenSizeSliderOptions);
gardenSizeSliderObject.on('slide', updateGardenSizeInputs);
function gardenSizeMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = gardenSizeSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
gardenSizeSliderObject.set([fixedNewValue, null]);
$("#gardenSizeMin").val(Math.round(gardenSizeSliderObject.get()[0]));
}
}
function gardenSizeMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
gardenSizeSliderObject.set([null, newValue]);
$("#gardenSizeMin").val(Math.round(gardenSizeSliderObject.get()[0]));
}
}
$("#gardenSizeMin").val(gardenSizeSliderOptions.start[0]);
$("#gardenSizeMax").val(gardenSizeSliderOptions.start[1]);
$("#gardenSizeMin").change("step", gardenSizeMinChangeHandler);
$("#gardenSizeMax").change("step", gardenSizeMaxChangeHandler);
<% } %>
$("#submit").click(function() {
const priceFilterValues = priceSlider.noUiSlider.get();
$("#priceFilterMin").val(priceFilterValues[0]);
$("#priceFilterMax").val(priceFilterValues[1]);
const sizeFilterValues = sizeSlider.noUiSlider.get();
$("#sizeFilterMin").val(sizeFilterValues[0]);
$("#sizeFilterMax").val(sizeFilterValues[1]);
<% if (hasGardenSize) { %>
const gardenSizeFilterValues = gardenSizeSlider.noUiSlider.get();
$("#gardenSizeFilterMin").val(gardenSizeFilterValues[0]);
$("#gardenSizeFilterMax").val(gardenSizeFilterValues[1]);
<% } %>
$("#filtersForm").submit();
});
$(document).ready(function(){
$('.tabs').tabs();
});
</script>

View File

@@ -0,0 +1,195 @@
<br />
<div class="row center-align">
<h5>Cijena (KM)</h5>
<br /><br />
<div class="col s12">
<div class="center-align no-ui-slider" id="priceFilter"></div>
</div>
</div>
<br />
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input class="sliderInputBox" type="number" id="priceMin" name="priceMin" />
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input class="sliderInputBox" type="number" id="priceMax" name="priceMax" />
</div>
</div>
<br />
<div class="row center-align">
<h5>Površina (m<sup>2</sup>)</h5>
<br /><br />
<div class="col s12">
<div class="center-align no-ui-slider" id="sizeFilter"></div>
</div>
</div>
<br />
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input class="sliderInputBox" type="number" id="sizeMin" name="sizeMin" />
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input class="sliderInputBox" type="number" id="sizeMax" name="sizeMax" />
</div>
</div>
<br />
<% if(hasGardenSize) { %>
<div class="row center-align">
<h5>Površina okućnice (m<sup>2</sup>)</h5>
<br /><br />
<div class="col s12">
<div class="center-align no-ui-slider" id="gardenSizeFilter"></div>
</div>
</div>
<br />
<div class="row">
<div class="col s5 m3 l3 push-m1 push-l2">
<input
class="sliderInputBox"
type="number"
id="gardenSizeMin"
name="gardenSizeMin"
/>
</div>
<div class="col s5 m3 l3 push-s1 push-m4 push-l4">
<input
class="sliderInputBox"
type="number"
id="gardenSizeMax"
name="gardenSizeMax"
/>
</div>
</div>
<% } %>
<script>
$(document).ready(function() {
const priceSliderOptions = <%- priceSliderOptions %>;
const sizeSliderOptions = <%- sizeSliderOptions %>;
const priceStep = priceSliderOptions.step;
const sizeStep = sizeSliderOptions.step;
delete priceSliderOptions.step;
delete sizeSliderOptions.step;
function updatePriceInputs(values, handle, unencoded) {
$("#priceMin").val(Math.round(unencoded[0]/priceStep)*priceStep);
$("#priceMax").val(Math.round(unencoded[1]/priceStep)*priceStep);
}
function updateSizeInputs(values, handle, unencoded) {
$("#sizeMin").val(Math.round(unencoded[0]/sizeStep)*sizeStep);
$("#sizeMax").val(Math.round(unencoded[1]/sizeStep)*sizeStep);
}
const priceSlider = document.getElementById("priceFilter");
const sizeSlider = document.getElementById("sizeFilter");
const priceSliderObject = noUiSlider.create(priceSlider, priceSliderOptions);
const sizeSliderObject = noUiSlider.create(sizeSlider, sizeSliderOptions);
priceSliderObject.on('slide', updatePriceInputs);
sizeSliderObject.on('slide', updateSizeInputs);
function priceMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = priceSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
priceSliderObject.set([fixedNewValue, null]);
$("#priceMin").val(Math.round(priceSliderObject.get()[0]));
}
}
function priceMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
priceSliderObject.set([null, newValue]);
$("#priceMax").val(Math.round(priceSliderObject.get()[1]));
}
}
$("#priceMin").val(priceSliderOptions.start[0]);
$("#priceMax").val(priceSliderOptions.start[1]);
$("#priceMin").change(priceMinChangeHandler);
$("#priceMax").change(priceMaxChangeHandler);
function sizeMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = sizeSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
sizeSliderObject.set([fixedNewValue, null]);
$("#sizeMin").val(Math.round(sizeSliderObject.get()[0]));
}
}
function sizeMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
sizeSliderObject.set([null, newValue]);
$("#sizeMax").val(Math.round(sizeSliderObject.get()[1]));
}
}
$("#sizeMin").val(sizeSliderOptions.start[0]);
$("#sizeMax").val(sizeSliderOptions.start[1]);
$("#sizeMin").change(sizeMinChangeHandler);
$("#sizeMax").change(sizeMaxChangeHandler);
<% if(hasGardenSize) { %>
const gardenSizeSliderOptions = <%- gardenSizeSliderOptions %>;
const gardenSizeStep = gardenSizeSliderOptions.step;
delete gardenSizeSliderOptions.step;
function updateGardenSizeInputs(values, handle, unencoded) {
$("#gardenSizeMin").val(Math.round(unencoded[0]/gardenSizeStep)*gardenSizeStep);
$("#gardenSizeMax").val(Math.round(unencoded[1]/gardenSizeStep)*gardenSizeStep);
}
const gardenSizeSlider = document.getElementById("gardenSizeFilter");
const gardenSizeSliderObject = noUiSlider.create(gardenSizeSlider, gardenSizeSliderOptions);
gardenSizeSliderObject.on('slide', updateGardenSizeInputs);
function gardenSizeMinChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const currentValues = gardenSizeSliderObject.get();
const newValue = element.currentTarget.value;
const fixedNewValue = newValue > currentValues[1] ? currentValues[1] : newValue;
gardenSizeSliderObject.set([fixedNewValue, null]);
$("#gardenSizeMin").val(Math.round(gardenSizeSliderObject.get()[0]));
}
}
function gardenSizeMaxChangeHandler(element) {
if (element && element.currentTarget && element.currentTarget.value){
const newValue = element.currentTarget.value;
gardenSizeSliderObject.set([null, newValue]);
$("#gardenSizeMin").val(Math.round(gardenSizeSliderObject.get()[0]));
}
}
$("#gardenSizeMin").val(gardenSizeSliderOptions.start[0]);
$("#gardenSizeMax").val(gardenSizeSliderOptions.start[1]);
$("#gardenSizeMin").change("step", gardenSizeMinChangeHandler);
$("#gardenSizeMax").change("step", gardenSizeMaxChangeHandler);
<% } %>
$("#submit").click(function() {
const priceFilterValues = priceSlider.noUiSlider.get();
$("#priceFilterMin").val(priceFilterValues[0]);
$("#priceFilterMax").val(priceFilterValues[1]);
const sizeFilterValues = sizeSlider.noUiSlider.get();
$("#sizeFilterMin").val(sizeFilterValues[0]);
$("#sizeFilterMax").val(sizeFilterValues[1]);
<% if (hasGardenSize) { %>
const gardenSizeFilterValues = gardenSizeSlider.noUiSlider.get();
$("#gardenSizeFilterMin").val(gardenSizeFilterValues[0]);
$("#gardenSizeFilterMax").val(gardenSizeFilterValues[1]);
<% } %>
$("#filtersForm").submit();
});
});
</script>

View File

@@ -14,6 +14,9 @@ MAX_REAL_ESTATES_IN_FIRST_EMAIL=Max number of real estates that will be shown in
#=============== GOOGLE ANALYTICS =============#
GA_ID=Google Analytics ID
#=============== GOOGLE MAPS =============#
API_MAP_KEY=(your-key-here)
#=============== AWS SDK EMAIL SETTINGS =======#
AWS_KEY_ID=(your-key-here)
AWS_SECRET_ACCESS_KEY=(your-key-here)

26
package-lock.json generated
View File

@@ -1052,6 +1052,14 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint-plugin-prettier": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
"integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1271,6 +1279,11 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@@ -3179,6 +3192,19 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew=="
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"requires": {
"fast-diff": "^1.1.2"
}
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",

View File

@@ -35,6 +35,7 @@
"compression": "^1.7.4",
"dotenv": "^7.0.0",
"ejs": "^2.6.1",
"eslint-plugin-prettier": "^3.1.2",
"express": "^4.16.4",
"express-ejs-layouts": "^2.5.0",
"express-layout": "^0.1.0",
@@ -44,6 +45,7 @@
"node-fetch": "^2.3.0",
"node-schedule": "^1.3.2",
"pg": "^7.10.0",
"prettier": "^1.19.1",
"react-step-wizard": "^5.1.0",
"sequelize": "^5.18.4",
"sequelize-cli": "^5.5.0"