I'm working on building out an analytic tool for my vacation rental management website. I currently use airtable as my backend to store the properties, bookings, owners, etc. I'm trying to create an object that contains each property associated with each booking paired together. I can't get the filter to create the record to work!
If I console log each step, the first property shows 89 linked bookings which include an ID and guest name. Mapping the ids to an array I have all 89 ids. However, when I go to filter my bookings by this id array it only returns 77 records. Next property iteration I get 6, 6 & an empty array. Then 24, 24, and another empty array.
const propertiesBookings = properties ? properties.map(property => {
const propertyLinkedBookings = property.getCellValue(propertiesBookingsLinkField);
console.log(propertyLinkedBookings);
const bookingIds = propertyLinkedBookings ? propertyLinkedBookings.map(a => a.id) : [];
console.log(bookingIds);
const filterById = new Set(bookingIds);
const propertyBookings = bookings ? bookings.filter(a => bookingIds.includes(a.id)) : [];
console.log(propertyBookings)
const propertyAppntmntsObj = {
property: property,
bookings: propertyBookings
};
return propertyAppntmntsObj;
}) : [];
Does anyone see any issues in the code or am I missing something in my data??
Full Code
import {
initializeBlock,
useBase,
useRecords,
useGlobalConfig,
useSettingsButton,
Box,
Heading,
ViewPickerSynced,
TablePickerSynced,
InputSynced,
FormField,
FieldPickerSynced,
createRecordsAsync,
hasPermissionToCreateRecords,
Button,
Text,
ViewportConstraint
// RecordCard
} from '@airtable/blocks/ui';
import React, {useState} from 'react';
import { FieldType } from '@airtable/blocks/models';
const GlobalConfigKeys = {
VIEW_ID: 'viewId',
PROPERTIES_TABLE_ID: 'propertiesTableId',
PROPERTIES_BOOKINGS_LINK_FIELD_ID: 'propertiesBookingsLinkFieldId',
PROPERTIES_NAME_FIELD_ID: 'propertiesNameFieldId',
BOOKINGS_TABLE_ID: 'bookingsTableId',
BOOKINGS_START_FIELD_ID: 'bookingsStartFieldId',
BOOKINGS_END_FIELD_ID: 'bookingsEndFieldId',
BOOKINGS_ADR_ID: 'bookingsAdrId',
PERFORMANCE_TABLE_ID: 'performanceTableId',
PERFORMANCE_PROPERTY_ID: 'performancePropertyId',
PERFORMANCE_DATE_ID: 'performanceDateId',
PERFORMANCE_ADR_ID: 'performanceAdrId',
PERFORMANCE_OCC_ID: 'performanceOccId',
START_DATE: 'startDate',
END_DATE: 'endDate'
};
const MAX_RECORDS_PER_UPDATE = 50;
function PropertyAnalyticsApp() {
const VIEWPORT_MIN_WIDTH = 345;
const VIEWPORT_MIN_HEIGHT = 200;
const base = useBase();
const globalConfig = useGlobalConfig();
const bookingsTableId = globalConfig.get(GlobalConfigKeys.BOOKINGS_TABLE_ID);
const bookingsStartFieldId = globalConfig.get(GlobalConfigKeys.BOOKINGS_START_FIELD_ID);
const bookingsEndFieldId = globalConfig.get(GlobalConfigKeys.BOOKINGS_END_FIELD_ID);
const bookingsAdrId = globalConfig.get(GlobalConfigKeys.BOOKINGS_ADR_ID);
const propertiesTableId = globalConfig.get(GlobalConfigKeys.PROPERTIES_TABLE_ID);
const propertiesNameFieldId = globalConfig.get(GlobalConfigKeys.PROPERTIES_NAME_FIELD_ID);
const propertiesBookingsLinkFieldId = globalConfig.get(GlobalConfigKeys.PROPERTIES_BOOKINGS_LINK_FIELD_ID);
const performanceTableId = globalConfig.get(GlobalConfigKeys.PERFORMANCE_TABLE_ID);
const performancePropertyId = globalConfig.get(GlobalConfigKeys.PERFORMANCE_PROPERTY_ID)
const performanceDateId = globalConfig.get(GlobalConfigKeys.PERFORMANCE_DATE_ID);
const performanceAdrId = globalConfig.get(GlobalConfigKeys.PERFORMANCE_ADR_ID);
const performanceOccId = globalConfig.get(GlobalConfigKeys.PERFORMANCE_OCC_ID);
var startDate = globalConfig.get(GlobalConfigKeys.START_DATE);
var endDate = globalConfig.get(GlobalConfigKeys.END_DATE);
var tempBooking = {};
const bookingRecords = new Array();
const initialSetupDone = bookingsTableId && bookingsStartFieldId && bookingsEndFieldId && bookingsAdrId &&
propertiesTableId && propertiesNameFieldId && propertiesBookingsLinkFieldId &&
performancePropertyId && performanceTableId && performanceDateId && performanceAdrId &&
performanceOccId && startDate && endDate ? true : false;
// Use settings menu to hide away table pickers
const [isShowingSettings, setIsShowingSettings] = useState(!initialSetupDone);
useSettingsButton(function() {
initialSetupDone && setIsShowingSettings(!isShowingSettings);
});
const bookingsTable = base.getTableByIdIfExists(bookingsTableId);
const bookingsStartField = initialSetupDone ?
bookingsTable.getFieldByIdIfExists(bookingsStartFieldId) : null;
const bookingsEndField = initialSetupDone ?
bookingsTable.getFieldByIdIfExists(bookingsEndFieldId): null;
const bookingsAdr = initialSetupDone ?
bookingsTable.getFieldByIdIfExists(bookingsAdrId): null;
const propertiesTable = base.getTableByIdIfExists(propertiesTableId);
const propertiesNameField = initialSetupDone ?
propertiesTable.getFieldByIdIfExists(propertiesNameFieldId) : null;
const propertiesBookingsLinkField = initialSetupDone ?
propertiesTable.getFieldByIdIfExists(propertiesBookingsLinkFieldId) : null;
const performanceTable = base.getTableByIdIfExists(performanceTableId);
const performanceProperty = initialSetupDone ?
performanceTable.getFieldByIdIfExists(performancePropertyId): null;
const performanceDate = initialSetupDone ?
performanceTable.getFieldByIdIfExists(performanceDateId): null;
const performanceAdr = initialSetupDone ?
performanceTable.getFieldByIdIfExists(performanceAdrId): null;
const performanceOcc = initialSetupDone ?
performanceTable.getFieldByIdIfExists(performanceOccId): null;
// The view ID is stored in globalConfig using ViewPickerSynced.
const viewId = globalConfig.get(GlobalConfigKeys.VIEW_ID);
const bookingsView = initialSetupDone ? bookingsTable.getViewByIdIfExists(viewId) : null;
const properties = useRecords(propertiesTable ? propertiesTable.selectRecords() : null);
const bookings = useRecords(bookingsView ? bookingsView.selectRecords() : null);
if (isShowingSettings) {
return (
<ViewportConstraint minSize={{width: VIEWPORT_MIN_WIDTH, height: VIEWPORT_MIN_HEIGHT}}>
<SettingsMenu
globalConfig={globalConfig}
base={base}
propertiesTable={propertiesTable}
bookingsTable={bookingsTable}
performanceTable={performanceTable}
initialSetupDone={initialSetupDone}
onDoneClick={() => setIsShowingSettings(false)}
/>
</ViewportConstraint>
)
} else {
if (bookings) {
// Get a property's linked bookings and match them with the property
const propertiesBookings = properties ? properties.map(property => {
const propertyLinkedBookings = property.getCellValue(propertiesBookingsLinkField);
console.log(propertyLinkedBookings);
const bookingIds = propertyLinkedBookings ? propertyLinkedBookings.map(a => a.id) : [];
console.log(bookingIds);
const propertyBookings = bookings ? bookings.filter(a => bookingIds.indexOf(a.id) === -1) : [];
console.log(propertyBookings)
const propertyAppntmntsObj = {
property: property,
bookings: propertyBookings
};
return propertyAppntmntsObj;
}) : [];
let bookingsRecord = new Array();
var loop = new Date(startDate);
var loopEnd = new Date(endDate);
console.log(propertiesBookings);
alert();
while(loop <= loopEnd) {
for(var x=0; x < properties.length; x++) {
propertiesBookings.forEach(propertyAppntmnts => {
const { property, bookings } = propertyAppntmnts;
console.log(bookings);
if(bookings.length != 0) {
for (var i=0; i < bookings.length; i++) {
var start = new Date(bookings[i].getCellValue(bookingsStartField));
var end = new Date(bookings[i].getCellValue(bookingsEndField));
var adr = bookings[i].getCellValue(bookingsAdr);
var house = property.getCellValue(propertiesNameField);
var flag = false;
console.log(property.getCellValue(propertiesNameField));
console.log(bookings.length);
console.log(start);
if (loop.getFullYear() >= start.getFullYear() && loop.getFullYear() <= end.getFullYear()) {
if (loop.getMonth() >= start.getMonth() && loop.getMonth() <= end.getMonth()) {
if (loop.getDate() >= start.getDate() && loop.getDate() < end.getDate()) {
flag = true;
break;
}
}
}
}
if (flag == true) {
bookingRecords.push({
fields: {
[performanceProperty.id]: house,
[performanceDate.id]: loop,
[performanceOcc.id]: true,
[performanceAdr.id]: adr
}
});
} else {
bookingRecords.push({
fields: {
[performanceProperty.id]: house,
[performanceDate.id]: loop,
[performanceOcc.id]: false,
[performanceAdr.id]: null
}
});
}
console.log(bookingRecords)
alert('pause');
flag = false;
}
});
}
}
let newDate = loop.setDate(loop.getDate() + 1);
loop = new Date(newDate);
}
}
return(
<ViewportConstraint minSize={{width: VIEWPORT_MIN_WIDTH, height: VIEWPORT_MIN_HEIGHT}}>
<Box paddingX={2} marginX={1}>
<Text size="xsmall" textColor="light">
View in {bookingsTable.name} to watch for conflicts:
</Text>
<ViewPickerSynced
table={bookingsTable}
globalConfigKey={GlobalConfigKeys.VIEW_ID}
autoFocus="true"
maxWidth="350px"
/>
<UpdateRecordsButton
records={bookingRecords}
tableToUpdate={performanceTable}
/>
</Box>
</ViewportConstraint>
)
}
function UpdateRecordsButton({records, tableToUpdate}) {
const [numRecordsBeingUpdated, setNumRecordsBeingUpdated] = useState(null);
const isUpdateInProgress = numRecordsBeingUpdated !== null;
let buttonText;
const recordsText = `record${records.length === 1 ? '' : 's'}`;
if (isUpdateInProgress) {
buttonText = `Updating ${numRecordsBeingUpdated} ${recordsText}`;
} else {
buttonText = `Click to update ${records.length} ${recordsText}`;
}
const shouldButtonBeDisabled =
isUpdateInProgress ||
records.length === 0 ||
!tableToUpdate.hasPermissionToUpdateRecords(records);
return (
<Button
variant="primary"
onClick={async function() {
// Mark the update as started.
setNumRecordsBeingUpdated(records.length);
// Update the records!
// await is used to wait for all of the updates to finish saving
// to Airtable servers. This keeps the button disabled until the
// update is finished.
await createRecordsInBatches(tableToUpdate, records);
// We're done! Mark the update as finished.
setNumRecordsBeingUpdated(null);
}}
disabled={shouldButtonBeDisabled}
>
{buttonText}
</Button>
);
}
async function createRecordsInBatches(table, records) {
let i = 0;
while (i< records.length) {
const createBatch = records.slice(i, i + MAX_RECORDS_PER_UPDATE);
await table.createRecordsAsync(createBatch);
i += MAX_RECORDS_PER_UPDATE;
}
alert('${records.length} records have been created');
}
function SettingsMenu(props) {
const resetBookingFieldKeys = () => {
props.globalConfig.setAsync(GlobalConfigKeys.BOOKINGS_START_FIELD_ID, '');
props.globalConfig.setAsync(GlobalConfigKeys.BOOKINGS_END_FIELD_ID, '');
};
const resetBookingsTableKey = () => {
props.globalConfig.setAsync(GlobalConfigKeys.BOOKINGS_TABLE_ID, '');
props.globalConfig.setAsync(GlobalConfigKeys.VIEW_ID, '');
};
const resetPropertyFieldKeys = () => {
props.globalConfig.setAsync(GlobalConfigKeys.PROPERTIES_NAME_FIELD_ID, '');
props.globalConfig.setAsync(GlobalConfigKeys.PROPERTIES_BOOKINGS_LINK_FIELD_ID, '');
}
const resetTableRelatedGlobalConfigKeys = () => {
resetBookingsTableKey();
resetBookingFieldKeys();
resetPropertyFieldKeys();
}
const getLinkedApptsTable = () => {
const linkedApptsFieldId = props.globalConfig.get(GlobalConfigKeys.PROPERTIES_BOOKINGS_LINK_FIELD_ID);
const propertiesTableId = props.globalConfig.get(GlobalConfigKeys.PROPERTIES_TABLE_ID);
const propertiesTable = props.base.getTableByIdIfExists(propertiesTableId);
const linkedApptsField = propertiesTable.getFieldByIdIfExists(linkedApptsFieldId);
const linkedTableId = linkedApptsField.options.linkedTableId;
props.globalConfig.setAsync(GlobalConfigKeys.BOOKINGS_TABLE_ID, linkedTableId);
};
return(
<div>
<Heading margin={2}>
Schedule Conflicts Settings
</Heading>
<Box marginX={2}>
<FormField label="Which table holds the Properties?">
<TablePickerSynced
globalConfigKey={GlobalConfigKeys.PROPERTIES_TABLE_ID}
onChange={() => resetTableRelatedGlobalConfigKeys()}
size="large"
maxWidth="350px"
/>
</FormField>
{props.propertiesTable &&
<div>
<Heading size="xsmall" variant="caps">{props.propertiesTable.name} Fields:</Heading>
<Box display="flex" flexDirection="row">
<FormField label="Name field:" marginRight={1}>
<FieldPickerSynced
size="small"
table={props.propertiesTable}
globalConfigKey={GlobalConfigKeys.PROPERTIES_NAME_FIELD_ID}
allowedTypes={[
FieldType.SINGLE_LINE_TEXT,
FieldType.FORMULA,
FieldType.AUTO_NUMBER,
FieldType.NUMBER,
FieldType.BARCODE,
FieldType.EMAIL,
FieldType.PHONE_NUMBER,
FieldType.URL,
FieldType.MULTILINE_TEXT
]}
/>
</FormField>
<FormField label="Events/Bookings linked field:">
<FieldPickerSynced
size="small"
table={props.propertiesTable}
globalConfigKey={GlobalConfigKeys.PROPERTIES_BOOKINGS_LINK_FIELD_ID}
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
onChange={() => getLinkedApptsTable()}
/>
</FormField>
</Box>
</div>
}
<hr/>
{props.bookingsTable &&
<div>
<FormField label="The table holding your Events/Bookings is:">
<Text size="xlarge">
{props.bookingsTable.name}
</Text>
</FormField>
<Heading size="xsmall" variant="caps">{props.bookingsTable.name} Fields:</Heading>
<Box display="flex" flexDirection="row">
<FormField label="Start date/time field:" marginRight={1}>
<FieldPickerSynced
size="small"
table={props.bookingsTable}
globalConfigKey={GlobalConfigKeys.BOOKINGS_START_FIELD_ID}
allowedTypes={[
FieldType.DATE,
FieldType.DATE_TIME,
FieldType.MULTIPLE_LOOKUP_VALUES,
FieldType.ROLLUP,
FieldType.FORMULA
]}
/>
</FormField>
<FormField label="End date/time field:">
<FieldPickerSynced
size="small"
table={props.bookingsTable}
globalConfigKey={GlobalConfigKeys.BOOKINGS_END_FIELD_ID}
allowedTypes={[
FieldType.DATE,
FieldType.DATE_TIME,
FieldType.MULTIPLE_LOOKUP_VALUES,
FieldType.ROLLUP,
FieldType.FORMULA
]}
/>
</FormField>
<FormField label="ADR:">
<FieldPickerSynced
size="small"
table={props.bookingsTable}
globalConfigKey={GlobalConfigKeys.BOOKINGS_ADR_ID}
allowedTypes={[
FieldType.CURRENCY,
FieldType.FORMULA
]}
/>
</FormField>
</Box>
</div>
}
<FormField label="Which table holds the Performance Numbers?">
<TablePickerSynced
globalConfigKey={GlobalConfigKeys.PERFORMANCE_TABLE_ID}
size="large"
maxWidth="350px"
/>
</FormField>
{props.performanceTable &&
<div>
<Heading size="xsmall" variant="caps">{props.performanceTable.name} Fields:</Heading>
<Box display="flex" flexDirection="row">
<FormField label="Property Field:" marginRight={1}>
<FieldPickerSynced
size="small"
table={props.performanceTable}
globalConfigKey={GlobalConfigKeys.PERFORMANCE_PROPERTY_ID}
allowedTypes={[
FieldType.SINGLE_LINE_TEXT,
FieldType.MULTILINE_TEXT,
FieldType.MULTIPLE_RECORD_LINKS,
FieldType.ROLLUP
]}
/>
</FormField>
<FormField label="Date:">
<FieldPickerSynced
size="small"
table={props.performanceTable}
globalConfigKey={GlobalConfigKeys.PERFORMANCE_DATE_ID}
allowedTypes={[
FieldType.DATE,
FieldType.DATE_TIME,
FieldType.MULTIPLE_LOOKUP_VALUES,
FieldType.ROLLUP,
FieldType.FORMULA
]}
/>
</FormField>
<FormField label="ADR:">
<FieldPickerSynced
size="small"
table={props.performanceTable}
globalConfigKey={GlobalConfigKeys.PERFORMANCE_ADR_ID}
allowedTypes={[
FieldType.CURRENCY,
FieldType.FORMULA
]}
/>
</FormField>
<FormField label="Occupied:">
<FieldPickerSynced
size="small"
table={props.performanceTable}
globalConfigKey={GlobalConfigKeys.PERFORMANCE_OCC_ID}
allowedTypes={[
FieldType.CHECKBOX
]}
/>
</FormField>
</Box>
</div>
}
<FormField label="Starting Date?">
<InputSynced
type= 'date'
globalConfigKey={GlobalConfigKeys.START_DATE}
size="large"
maxWidth="350px"
/>
</FormField>
<FormField label="Ending Date?">
<InputSynced
type= 'date'
globalConfigKey={GlobalConfigKeys.END_DATE}
size="large"
maxWidth="350px"
/>
</FormField>
</Box>
<Box display="flex" marginBottom={2}>
<Button
variant="primary"
icon="check"
marginLeft={2}
disabled={!props.initialSetupDone}
onClick={props.onDoneClick}
alignSelf="right"
>
Done
</Button>
</Box>
</div>
);
}
initializeBlock(() => <PropertyAnalyticsApp />)