Compare commits

..

3 Commits

Author SHA1 Message Date
Naida Vatric
d23ddf849f Results title text made into link. 2020-01-07 01:06:22 +01:00
Naida Vatric
38bd0343f5 Merge branch 'results-link' of gitlab.com:saburly/marketalarm/web into no-all-results-email 2020-01-07 01:01:57 +01:00
Naida Vatric
fa4e0d64de Changed email content to show number of all matching real estates. 2020-01-06 23:59:56 +01:00
11 changed files with 171 additions and 492 deletions

View File

@@ -1 +0,0 @@
*.ejs

View File

@@ -35,8 +35,7 @@ const getFilters = async (req, res) => {
balcony, balcony,
elevator, elevator,
newBuilding, newBuilding,
accessRoadType, accessRoadType
includeWithoutPrice
} = searchRequest; } = searchRequest;
const category = AD_CATEGORY[realEstateType] || AD_CATEGORY.FLAT; const category = AD_CATEGORY[realEstateType] || AD_CATEGORY.FLAT;
@@ -116,8 +115,7 @@ const getFilters = async (req, res) => {
advancedSegmentSelectFilterValues, advancedSegmentSelectFilterValues,
advancedRangeFilterObjects, advancedRangeFilterObjects,
advancedRangeFilterValues, advancedRangeFilterValues,
includeIncompleteAds, includeIncompleteAds
includeWithoutPrice
}); });
}; };
@@ -193,7 +191,6 @@ const postFilters = async (req, res) => {
}); });
const includeIncompleteAds = req.body.includeIncompleteAds === "on"; const includeIncompleteAds = req.body.includeIncompleteAds === "on";
const includeWithoutPrice = req.body.includeWithoutPrice === "on";
const balcony = req.body.balcony === "on"; const balcony = req.body.balcony === "on";
const elevator = req.body.elevator === "on"; const elevator = req.body.elevator === "on";
@@ -220,7 +217,6 @@ const postFilters = async (req, res) => {
searchRequest.newBuilding = newBuilding; searchRequest.newBuilding = newBuilding;
searchRequest.includeIncompleteAds = includeIncompleteAds; searchRequest.includeIncompleteAds = includeIncompleteAds;
searchRequest.includeWithoutPrice = includeWithoutPrice;
searchRequest.accessRoadType = accessRoadType; searchRequest.accessRoadType = accessRoadType;

View File

@@ -2,8 +2,6 @@
const db = require("../../models/index"); const db = require("../../models/index");
const sequelize = require("sequelize"); const sequelize = require("sequelize");
const Op = sequelize.Op; const Op = sequelize.Op;
const { AD_CATEGORY } = require("../../common/enums");
const bulkUpsertRealEstates = async realEstateData => { const bulkUpsertRealEstates = async realEstateData => {
try { try {
const fieldsToUpdateIfDuplicate = [ const fieldsToUpdateIfDuplicate = [
@@ -98,16 +96,12 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
floorMin, floorMin,
floorMax, floorMax,
includeIncompleteAds, includeIncompleteAds,
includeWithoutPrice,
balcony, balcony,
elevator, elevator,
newBuilding, newBuilding,
accessRoadType accessRoadType
} = searchRequest; } = searchRequest;
//Needed for defining which attribute should exist or not
const realEstateTypeObject = AD_CATEGORY[realEstateType];
const longitudeColumn = sequelize.col("locationLong"); const longitudeColumn = sequelize.col("locationLong");
const latitudeColumn = sequelize.col("locationLat"); const latitudeColumn = sequelize.col("locationLat");
@@ -140,6 +134,15 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
const query = { const query = {
adType, adType,
realEstateType, realEstateType,
price: {
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
},
area: { area: {
[Op.lte]: sizeMax, [Op.lte]: sizeMax,
[Op.gte]: sizeMin [Op.gte]: sizeMin
@@ -151,6 +154,15 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
const queryIncludeIncomplete = { const queryIncludeIncomplete = {
adType, adType,
realEstateType, realEstateType,
price: {
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
},
area: { area: {
[Op.or]: { [Op.or]: {
[Op.and]: { [Op.and]: {
@@ -163,49 +175,8 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
[Op.and]: geoSearchQueryPart [Op.and]: geoSearchQueryPart
}; };
//Is user unchecked includeWithoutPrice FALSE then it shouldn't return null values of price //Every other attribute is checked separately and included in query only if it is defined
//If not then null values are accepted (this is DEFAULT) if (gardenSizeMax && gardenSizeMin) {
//includeIncpompleteAds does not have effect on price query
if (includeWithoutPrice) {
query.price = {
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
};
queryIncludeIncomplete.price = {
[Op.or]: {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
},
[Op.is]: null
}
};
} else {
query.price = {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
}
};
queryIncludeIncomplete.price = {
[Op.and]: {
[Op.lte]: priceMax,
[Op.gte]: priceMin
}
};
}
//Every other attribute is checked separately and included in query only if it is defined for real estate type
if (
realEstateTypeObject.hasGardenSize &&
gardenSizeMax != null &&
gardenSizeMin != null
) {
query.gardenSize = { query.gardenSize = {
[Op.lte]: gardenSizeMax, [Op.lte]: gardenSizeMax,
[Op.gte]: gardenSizeMin [Op.gte]: gardenSizeMin
@@ -221,11 +192,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
}; };
} }
if ( if (numberOfRoomsMin && numberOfRoomsMax) {
realEstateTypeObject.hasNumberOfRoom &&
numberOfRoomsMin != null &&
numberOfRoomsMax != null
) {
query.numberOfRooms = { query.numberOfRooms = {
[Op.lte]: numberOfRoomsMax, [Op.lte]: numberOfRoomsMax,
[Op.gte]: numberOfRoomsMin [Op.gte]: numberOfRoomsMin
@@ -241,11 +208,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
}; };
} }
if ( if (numberOfFloorsMin && numberOfFloorsMax) {
realEstateTypeObject.hasNumberOfFloors &&
numberOfFloorsMin != null &&
numberOfFloorsMax != null
) {
query.numberOfFloors = { query.numberOfFloors = {
[Op.lte]: numberOfFloorsMax, [Op.lte]: numberOfFloorsMax,
[Op.gte]: numberOfFloorsMin [Op.gte]: numberOfFloorsMin
@@ -261,11 +224,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
}; };
} }
if ( if (floorMin && floorMax) {
realEstateTypeObject.hasFloorProp &&
floorMin != null &&
floorMax != null
) {
query.floor = { query.floor = {
[Op.lte]: floorMax, [Op.lte]: floorMax,
[Op.gte]: floorMin [Op.gte]: floorMin
@@ -280,10 +239,8 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
} }
}; };
} }
//Logic for balcony, newBuilding and elevator from users side
//If true is checked, then I want characteristic to be true but, if (balcony) {
//if it is not checked, then I dont care - it can be null or false or true
if (realEstateTypeObject.hasBalconyProp && balcony === true) {
query.balcony = { query.balcony = {
[Op.eq]: balcony [Op.eq]: balcony
}; };
@@ -295,7 +252,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
}; };
} }
if (realEstateTypeObject.hasNewBuildingProp && newBuilding === true) { if (newBuilding) {
query.newBuilding = { query.newBuilding = {
[Op.eq]: newBuilding [Op.eq]: newBuilding
}; };
@@ -307,7 +264,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
}; };
} }
if (realEstateTypeObject.hasElevatorProp && elevator === true) { if (elevator) {
query.elevator = { query.elevator = {
[Op.eq]: elevator [Op.eq]: elevator
}; };
@@ -318,8 +275,7 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
} }
}; };
} }
//If user wants 'ANY' road type acces then it is not included in query -
//returns every road type and null values
if (accessRoadType !== "ANY") { if (accessRoadType !== "ANY") {
query.accessRoadType = { query.accessRoadType = {
[Op.eq]: accessRoadType [Op.eq]: accessRoadType

View File

@@ -49,390 +49,128 @@ const findSearchRequestsForRealEstate = async realEstate => {
const geoSearchQueryPart = sequelize.where(contains, true); const geoSearchQueryPart = sequelize.where(contains, true);
//Needed for defining which attribute should exist or not //General query contains only attributes that are defined for every RealEstate - not null
const realEstateTypeObject = AD_CATEGORY[realEstateType];
// ?? Needed to decide on including incomplete RealEstates data
let checkForIncompleteWanted = false;
//Attributes are checked separately to make different query parts
//If real estate price is number then it searches for req that have priceMin and priceMax
//If real estate price is null it searches for req that accept ads without price
//User always defines price and area (sliders) - not null in search req
let priceQuery = {};
if (price != null) {
priceQuery = {
[Op.and]: [
{
priceMin: {
[Op.lte]: price
}
},
{
priceMax: {
[Op.gte]: price
}
}
]
};
} else {
priceQuery = {
includeWithoutPrice: {
[Op.eq]: true
}
};
}
let areaQuery = {};
if (area != null) {
areaQuery = {
[Op.and]: [
{
sizeMin: {
[Op.lte]: area
}
},
{
sizeMax: {
[Op.gte]: area
}
}
]
};
} else {
checkForIncompleteWanted = true;
}
//Other attributes can be defined or not depending on RealEstate type
//we check what to include in query based on real estate type object
let gardenSizeQuery = {};
if (realEstateTypeObject.hasGardenSize) {
if (gardenSize != null) {
gardenSizeQuery = {
[Op.and]: [
{
gardenSizeMin: {
[Op.lte]: gardenSize
}
},
{
gardenSizeMax: {
[Op.gte]: gardenSize
}
}
]
};
} else {
checkForIncompleteWanted = true;
}
}
let numberOfRoomsQuery = {};
if (realEstateTypeObject.hasNumberOfRoom) {
if (numberOfRooms != null) {
//If real estate has defined number of rooms ex. 3 it returns req
// that accepts 3 rooms or ones that don't have defined number - null
//Ex. they didnt choose advanced filters at all
numberOfRoomsQuery = {
[Op.and]: [
{
numberOfRoomsMin: {
[Op.or]: {
[Op.lte]: numberOfRooms,
[Op.is]: null
}
}
},
{
numberOfRoomsMax: {
[Op.or]: {
[Op.gte]: numberOfRooms,
[Op.is]: null
}
}
}
]
};
} else {
// If real estate dont have defined number of rooms ex. null
//It returns requests that didn't choose number of rooms - also null
//Or ones that picked some values but also picked to includeIncomplete ads
numberOfRoomsQuery = {
[Op.or]: [
{
[Op.and]: [
{
numberOfRoomsMin: {
[Op.is]: null
}
},
{
numberOfRoomsMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
};
}
}
//Same logic for number of Floors and floors
let numberOfFloorsQuery = {};
if (realEstateTypeObject.hasNumberOfFloors) {
if (numberOfFloors != null) {
numberOfFloorsQuery = {
[Op.and]: [
{
numberOfFloorsMin: {
[Op.or]: {
[Op.lte]: numberOfFloors,
[Op.is]: null
}
}
},
{
numberOfFloorsMax: {
[Op.or]: {
[Op.gte]: numberOfFloors,
[Op.is]: null
}
}
}
]
};
} else {
numberOfFloorsQuery = {
[Op.or]: [
{
[Op.and]: [
{
numberOfFloorsMin: {
[Op.is]: null
}
},
{
numberOfFloorsMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
};
}
}
let floorQuery = {};
if (realEstateTypeObject.hasFloorProp) {
if (floor != null) {
floorQuery = {
[Op.and]: [
{
floorMin: {
[Op.or]: {
[Op.lte]: floor,
[Op.is]: null
}
}
},
{
floorMax: {
[Op.or]: {
[Op.gte]: floor,
[Op.is]: null
}
}
}
]
};
} else {
floorQuery = {
[Op.or]: [
{
[Op.and]: [
{
floorMin: {
[Op.is]: null
}
},
{
floorMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
};
}
}
//Logic for balcony, newBuilding and elevator
//If user dont check checkbox for ex. elevator it does not mean he only wants no elevator
//If real estate characteristic =true find all req, one that wants charachertistic or dont care - dont need query
//If real estate characteristic = false, find all req exept for ones that wants characteristic to be true
//If real estate characteristic = null, dont know if true or false, find req that dont care or want char and want incomplete ads
let balconyQuery = {};
if (realEstateTypeObject.hasBalconyProp && balcony !== true) {
if (balcony === false) {
balconyQuery = {
balcony: {
[Op.ne]: true
}
};
} else if (balcony === null) {
balconyQuery = {
[Op.or]: [
{
balcony: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
balcony: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
}
]
};
}
}
let newBuildingQuery = {};
if (realEstateTypeObject.hasNewBuildingProp && newBuilding !== true) {
if (newBuilding === false) {
newBuildingQuery = {
newBuilding: {
[Op.ne]: true
}
};
} else if (newBuilding === null) {
newBuildingQuery = {
[Op.or]: [
{
newBuilding: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
newBuilding: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
}
]
};
}
}
let elevatorQuery = {};
if (realEstateTypeObject.hasElevatorProp && elevator !== true) {
if (elevator === false) {
elevatorQuery = {
elevator: {
[Op.ne]: true
}
};
} else if (elevator === null) {
elevatorQuery = {
[Op.or]: [
{
elevator: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
elevator: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.eq]: true
}
}
]
}
]
};
}
}
//General query consists of each individual query
const query = { const query = {
adType, adType,
realEstateType, realEstateType,
subscribed: true, subscribed: true,
[Op.and]: [ [Op.and]: geoSearchQueryPart
geoSearchQueryPart,
priceQuery,
areaQuery,
gardenSizeQuery,
numberOfRoomsQuery,
numberOfFloorsQuery,
floorQuery,
balconyQuery,
newBuildingQuery,
elevatorQuery
]
}; };
//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;
//AccessRoadType is defined - should exists for each ad and estate type //Attributes are checked separately and included in query only if defined
if (accessRoadType != null) { //Price and area should be defined for every property
if (price) {
query.priceMin = {
[Op.lte]: price
};
query.priceMax = {
[Op.gte]: price
};
}
if (area) {
query.sizeMin = {
[Op.lte]: area
};
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 = { query.accessRoadType = {
[Op.or]: { [Op.or]: {
[Op.like]: "ANY", [Op.eq]: "ANY",
[Op.eq]: accessRoadType [Op.eq]: accessRoadType
} }
}; };
} else { } else if (realEstateTypeObject.hasAccesRoadType) {
//Null values are returned for user request that wanted ANY acces road type checkForIncompleteWanted = true;
query.accessRoadType = {
[Op.eq]: "ANY"
};
} }
//Tag to check if incomplete ads are accepted in query
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) { if (checkForIncompleteWanted) {
query.includeIncompleteAds = { query.includeIncompleteAds = {
[Op.eq]: true [Op.eq]: true
}; };
} }
return await db.SearchRequest.findAll({ where: query });
return await db.SearchRequest.findAll({
where: query
});
}; };
module.exports = { module.exports = {

View File

@@ -23,16 +23,19 @@ const generateRealEstateLinks = realEstates => {
const generateNotificationEmail = ( const generateNotificationEmail = (
realEstates, realEstates,
searchRequestId, searchRequestId,
noAllRealEstates,
dailyNotification = false dailyNotification = false
) => { ) => {
const truncateList = realEstates.length > MAX_REAL_ESTATES_IN_EMAIL; const truncateList = realEstates.length > MAX_REAL_ESTATES_IN_EMAIL;
const realEstatesToShow = truncateList const realEstatesToShow = truncateList
? realEstates.slice(0, MAX_REAL_ESTATES_IN_EMAIL) ? realEstates.slice(0, MAX_REAL_ESTATES_IN_EMAIL)
: realEstates; : realEstates;
const allRealEstatesLink = `${APP_URL}/nekretnine/${searchRequestId}`; const allRealEstatesLink = `${APP_URL}/nekretnine/${searchRequestId}`;
const realEstateLinks = generateRealEstateLinks(realEstatesToShow); const realEstateLinks = generateRealEstateLinks(realEstatesToShow);
const moreRealEstates = `<div>Kompletan spisak nekretnina možete pogledati na <a href="${allRealEstatesLink}">listi nekretnina</a><div>`; const moreRealEstates = `<div>Kompletan spisak nekretnina (${noAllRealEstates}) možete pogledati na <a href="${allRealEstatesLink}">listi nekretnina</a><div>`;
const emailFooter = generateEmailFooter(searchRequestId); const emailFooter = generateEmailFooter(searchRequestId);
const asapMessageBody = const asapMessageBody =
realEstates.length > 1 realEstates.length > 1
@@ -70,6 +73,7 @@ const generateNewSearchRequestEmail = (searchRequest, matchingRealEstates) => {
} = searchRequest; } = searchRequest;
const realEstateLinks = generateRealEstateLinks(matchingRealEstates); const realEstateLinks = generateRealEstateLinks(matchingRealEstates);
const instantRealEstatesText = `<br/> const instantRealEstatesText = `<br/>
<div> <div>
U međuvremenu pogledajte neke od nedavno objavljenih nekretnina koje odgovaraju Vašim uslovima pretrage :<br/> U međuvremenu pogledajte neke od nedavno objavljenih nekretnina koje odgovaraju Vašim uslovima pretrage :<br/>

View File

@@ -1,14 +0,0 @@
"use strict";
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn("SearchRequests", "includeWithoutPrice", {
type: Sequelize.BOOLEAN,
defaultValue: true
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn("SearchRequests", "includeWithoutPrice");
}
};

View File

@@ -15,15 +15,7 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false, allowNull: false,
defaultValue: { defaultValue: {
type: "Polygon", type: "Polygon",
coordinates: [ coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]],
[
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]
]
],
crs: { type: "name", properties: { name: "EPSG:4326" } } crs: { type: "name", properties: { name: "EPSG:4326" } }
} }
}, },
@@ -79,7 +71,6 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.TEXT type: DataTypes.TEXT
}, },
includeIncompleteAds: DataTypes.BOOLEAN, includeIncompleteAds: DataTypes.BOOLEAN,
includeWithoutPrice: DataTypes.BOOLEAN,
balcony: DataTypes.BOOLEAN, balcony: DataTypes.BOOLEAN,
elevator: DataTypes.BOOLEAN, elevator: DataTypes.BOOLEAN,
newBuilding: DataTypes.BOOLEAN, newBuilding: DataTypes.BOOLEAN,

View File

@@ -154,3 +154,7 @@ h3 {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.estates-link {
color: rgba(0, 0, 0, 0.87);
}

View File

@@ -8,7 +8,10 @@ const {
generateNewSearchRequestEmail, generateNewSearchRequestEmail,
generateEmailSubject generateEmailSubject
} = require("../helpers/emailContentGenerator"); } = require("../helpers/emailContentGenerator");
const { findNotNotifiedMatches } = require("../helpers/db/searchRequestMatch"); const {
findNotNotifiedMatches,
findRealEstatesForSearchRequest
} = require("../helpers/db/searchRequestMatch");
const { sendEmail } = require("../services/emailService"); const { sendEmail } = require("../services/emailService");
const notifyForNewRealEstates = async newRealEstates => { const notifyForNewRealEstates = async newRealEstates => {
@@ -39,10 +42,18 @@ const notifyMatches = async (matches, dailyNotification = false) => {
const { email, subscribed } = searchRequest; const { email, subscribed } = searchRequest;
if (notifyNow && subscribed) { if (notifyNow && subscribed) {
const allMatchingRealEstates = matches[id].realEstates || []; const allMatchingRealEstates = matches[id].realEstates || [];
//Variable allMatchingRealEstates are real estates that are "new" on the market
//the ones that we notify user in this moment, not all that already exists in db
//New variable allRealEstates are all real estates that exists in db for search req
const allRealEstates = await findRealEstatesForSearchRequest(id);
const noAllRealEstates = allRealEstates.length;
if (allMatchingRealEstates.length > 0) { if (allMatchingRealEstates.length > 0) {
const emailContent = generateNotificationEmail( const emailContent = generateNotificationEmail(
allMatchingRealEstates, allMatchingRealEstates,
id, id,
noAllRealEstates,
dailyNotification dailyNotification
); );
const emailSubject = generateEmailSubject( const emailSubject = generateEmailSubject(

View File

@@ -1,13 +1,16 @@
<div class="row center-align"> <div class="row center-align">
<ul class="collection with-header"> <ul class="collection with-header">
<% for(const realEstate of realEstates) { %> <% for(const realEstate of realEstates) { %>
<li class="collection-item"> <li class="collection-item">
<div><%= realEstate.title %> <div>
<a href="<%= realEstate.url %>" class="kivi-color secondary-content"> <a href="<%= realEstate.url %>" class="estates-link">
<%= realEstate.title %>
<div class="kivi-color secondary-content">
<i class="material-icons">send</i> <i class="material-icons">send</i>
</a> </div>
</div> </a>
</li> </div>
<% } %> </li>
</ul> <% } %>
</div> </ul>
</div>

View File

@@ -18,15 +18,6 @@
</div> </div>
</div> </div>
<br />
<p class="distinguished">
<label class="checkbox-label">
<input type="checkbox" class="filled-in" name="includeWithoutPrice"
checked
>
<span>Uključi i oglase bez cijene</span>
</label>
</p>
<br /> <br />
<div class="row center-align"> <div class="row center-align">