1

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 />)
  • @Spectric Just uploaded full code – TimothyChurch Sep 17 '21 at 21:57
  • I recommend you to use lodash.io https://lodash.com/docs/4.17.15#filter – trysetyoutomo Sep 17 '21 at 22:40
  • I"m not familiar with lodash. Judging from a quick search I need to import lodash & underscore into the program. How do I define _ after importing them? – TimothyChurch Sep 17 '21 at 23:03
  • can you try it ? https://codesandbox.io/s/lodash-17n56 – trysetyoutomo Sep 17 '21 at 23:07
  • My instincts are telling me this might a classic `.includes()` vs. `.some()` scenario in which `bookings` is actually an array of objects and `.includes()` is coughing because of the `===` requirement. Link to explanation: https://d7k.medium.com/js-includes-vs-some-b3cd546a7bc3 – bill-felix Sep 30 '21 at 20:36

0 Answers0