Skip to content

Commit

Permalink
view+recalculate overlap: only show cohorts in same iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
domdomegg committed Jan 30, 2024
1 parent 2c762b2 commit b8fc399
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 25 deletions.
3 changes: 3 additions & 0 deletions frontend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type Preset = {
cohortsTable?: string;
cohortsTableStartDateField?: string;
cohortsTableEndDateField?: string;
cohortsIterationField?: string;
};

const createPreset = (name: string) => ({
Expand Down Expand Up @@ -149,13 +150,15 @@ function App() {
preset.cohortsTable &&
preset.cohortsTableStartDateField &&
preset.cohortsTableEndDateField &&
preset.cohortsIterationField &&
Object.keys(preset.personTypes).length > 0 &&
Object.values(preset.personTypes).every((personType) => (
personType.name &&
personType.sourceTable &&
personType.timeAvField &&
personType.howManyTypePerCohort &&
personType.howManyCohortsPerType &&
personType.iterationField &&
personType.cohortsTableField
));

Expand Down
27 changes: 16 additions & 11 deletions frontend/other.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const OtherPage = () => {
fields: [
preset.cohortsTableStartDateField,
preset.cohortsTableEndDateField,
preset.cohortsIterationField,
],
});
const cohortsWithTimes = rawCohorts.flatMap((cohort) => {
Expand All @@ -63,6 +64,7 @@ const OtherPage = () => {
return [{
id: cohort.id,
name: cohort.name,
iteration: cohort.getCellValueAsString(preset.cohortsIterationField),
timeAv,
}];
});
Expand All @@ -74,46 +76,49 @@ const OtherPage = () => {
console.log("updating", personType.name);

const table = base.getTableByIdIfExists(personType.sourceTable);
const view = personType.sourceView ? table.getViewById(personType.sourceView) : table

const records = (await table.selectRecordsAsync()).records;
const persons = (await view.selectRecordsAsync()).records;
const updatedRecords = [];
for (const record of records) {
for (const person of persons) {
try {
const parsedTimeAv = parseTimeAvString(
record.getCellValueAsString(personType.timeAvField)
const personTimeAv = parseTimeAvString(
person.getCellValueAsString(personType.timeAvField)
);

const iterationCohorts = cohortsWithTimes.filter(c => c.iteration === person.getCellValueAsString(personType.iterationField))

const fields = {};
if (personType.cohortOverlapFullField) {
fields[personType.cohortOverlapFullField] = cohortsWithTimes
fields[personType.cohortOverlapFullField] = iterationCohorts
.filter((cohort) => {
const [[mb, me]] = parseTimeAvString(cohort.timeAv);
return parsedTimeAv.some(([b, e]) => mb >= b && me <= e);
return personTimeAv.some(([b, e]) => mb >= b && me <= e);
})
.map(({ id }) => ({ id }));
}

if (personType.cohortOverlapPartialField) {
fields[personType.cohortOverlapPartialField] = cohortsWithTimes
fields[personType.cohortOverlapPartialField] = iterationCohorts
.filter((cohort) => {
const [[mb, me]] = parseTimeAvString(cohort.timeAv);
return parsedTimeAv.some(
return personTimeAv.some(
([b, e]) => (mb >= b && mb < e) || (me > b && me <= e)
);
})
.map(({ id }) => ({ id }));
}

const newRecord = {
id: record.id,
id: person.id,
fields,
};
updatedRecords.push(newRecord);
} catch (throwable: unknown) {
const prefix = `In processing person "${record.name}" (${record.id}): `;
const prefix = `In processing person "${person.name}" (${person.id}): `;
const error: Error = throwable instanceof Error ? throwable : new Error(String(throwable))
error.message = prefix + error.message;
(error as { record?: Record }).record = record;
(error as { record?: Record }).record = person;
throw error;
}
}
Expand Down
35 changes: 28 additions & 7 deletions frontend/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useSynced,
ViewPickerSynced
} from "@airtable/blocks/ui";
import { FieldType } from "@airtable/blocks/models";
import React, { useMemo, useState } from "react";
import { Preset } from ".";
import { MS_IN_MINUTE, MS_IN_WEEK, MINUTES_IN_UNIT } from "../lib/constants";
Expand All @@ -29,6 +30,7 @@ export type PersonType = {
timeAvField?: string;
cohortOverlapFullField?: string;
cohortOverlapPartialField?: string;
iterationField?: string,
howManyTypePerCohort?: [number, number];
howManyCohortsPerType?: number | string;
cohortsTableField?: string;
Expand Down Expand Up @@ -100,6 +102,7 @@ const PersonTypeComp = (props) => {
<FieldPickerSynced
table={cohortsTable}
placeholder="Pick cohort link field..."
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
globalConfigKey={[...path, "cohortsTableField"]}
/>
</div>
Expand Down Expand Up @@ -179,18 +182,28 @@ const PersonTypeComp = (props) => {
<FormField label="Time availability field">
<FieldPickerSynced
table={sourceTable}
allowedTypes={[FieldType.SINGLE_LINE_TEXT]}
globalConfigKey={[...path, "timeAvField"]}
/>
</FormField>
<FormField label="Iteration field">
<FieldPickerSynced
table={sourceTable}
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
globalConfigKey={[...path, "iterationField"]}
/>
</FormField>
<FormField label="Cohort full overlap field (optional)">
<FieldPickerSynced
table={sourceTable}
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
globalConfigKey={[...path, "cohortOverlapFullField"]}
/>
</FormField>
<FormField label="Cohort partial overlap field (optional)">
<FieldPickerSynced
table={sourceTable}
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
globalConfigKey={[...path, "cohortOverlapPartialField"]}
/>
</FormField>
Expand Down Expand Up @@ -328,7 +341,8 @@ const SetupPage = () => {
const cohortsTableConfigured =
preset.cohortsTable &&
preset.cohortsTableStartDateField &&
preset.cohortsTableEndDateField;
preset.cohortsTableEndDateField &&
preset.cohortsIterationField;

const typesOfPeopleConfigured =
Object.keys(preset.personTypes).length > 0 &&
Expand Down Expand Up @@ -392,29 +406,36 @@ const SetupPage = () => {
globalConfigKey={[...path, "cohortsTable"]}
onChange={() => {
globalConfig.setPathsAsync([
{
path: [...path, "cohortsTableStartDateField"],
value: null,
},
{ path: [...path, "cohortsTableStartDateField"], value: null },
{ path: [...path, "cohortsTableEndDateField"], value: null },
{ path: [...path, "cohortsIterationField"], value: null },
]);
}}
/>
</FormField>
{cohortsTable && (
<div className="grid sm:grid-cols-2 gap-1">
<FormField label="Start date field">
<FormField label="First session start time field">
<FieldPickerSynced
table={cohortsTable}
allowedTypes={[FieldType.DATE_TIME]}
globalConfigKey={[...path, "cohortsTableStartDateField"]}
/>
</FormField>
<FormField label="End date field">
<FormField label="First session end time field">
<FieldPickerSynced
table={cohortsTable}
allowedTypes={[FieldType.DATE_TIME]}
globalConfigKey={[...path, "cohortsTableEndDateField"]}
/>
</FormField>
<FormField label="Iteration field">
<FieldPickerSynced
table={cohortsTable}
allowedTypes={[FieldType.MULTIPLE_RECORD_LINKS]}
globalConfigKey={[...path, "cohortsIterationField"]}
/>
</FormField>
</div>
)}
</div>
Expand Down
18 changes: 11 additions & 7 deletions frontend/view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const ViewPerson = ({ tableId, recordId }) => {
});
const personType: PersonType = preset.personTypes[personTypeId];

const timeAv = parseTimeAvString(record.getCellValueAsString(personType.timeAvField));
const personTimeAv = parseTimeAvString(record.getCellValueAsString(personType.timeAvField));

const [overlapType, setOverlapType] = useState<"full" | "partial">("full");

Expand All @@ -50,6 +50,7 @@ const ViewPerson = ({ tableId, recordId }) => {
fields: [
preset.cohortsTableStartDateField,
preset.cohortsTableEndDateField,
preset.cohortsIterationField,
],
});
const cohortsWithTimes = rawCohorts.map((cohort) => {
Expand All @@ -69,25 +70,28 @@ const ViewPerson = ({ tableId, recordId }) => {
return {
id: cohort.id,
name: cohort.name,
iteration: cohort.getCellValueAsString(preset.cohortsIterationField),
timeAv: meetingDates.some(d => isNaN(d.getTime())) ? null : timeAv,
};
}).filter(c => c.timeAv);

const cohortsFull = cohortsWithTimes.filter((cohort) => {
const iterationCohorts = cohortsWithTimes.filter(c => c.iteration === record.getCellValueAsString(personType.iterationField))

const cohortsFull = iterationCohorts.filter((cohort) => {
const [[mb, me]] = parseTimeAvString(cohort.timeAv);
return timeAv.some(([b, e]) => mb >= b && me <= e);
return personTimeAv.some(([b, e]) => mb >= b && me <= e);
});

const cohortsPartial = cohortsWithTimes.filter((cohort) => {
const cohortsPartial = iterationCohorts.filter((cohort) => {
const [[mb, me]] = parseTimeAvString(cohort.timeAv);
return timeAv.some(
return personTimeAv.some(
([b, e]) => (mb >= b && mb < e) || (me > b && me <= e)
);
});

const [hoveredCohort, setHoveredCohort] = useState(null);

if (timeAv.length === 0) {
if (personTimeAv.length === 0) {
return (
<div className="text-gray-700">
Participant hasn&apos;t filled out the time availability form.
Expand All @@ -101,7 +105,7 @@ const ViewPerson = ({ tableId, recordId }) => {
</Heading>
<TimeAvWidget
availabilities={[{
intervals: timeAv,
intervals: personTimeAv,
class: "bg-green-500",
}, {
intervals: hoveredCohort ? parseTimeAvString(hoveredCohort.timeAv) : [],
Expand Down

0 comments on commit b8fc399

Please sign in to comment.