Fix Monday.com push: format column values by column type
The root cause was sending raw text to checkbox, status, dropdown, and
date columns. Monday.com requires specific JSON structures per type.
- Store column types in _columnTypes when Fetch Columns is clicked
- Format boolean/checkbox: {"checked": "true"/"false"}
- Format status/color: {"label": "value"}
- Format dropdown: {"labels": ["val1", "val2"]}
- Format date: parse MM/DD/YYYY to {"date": "YYYY-MM-DD"}
- Format email/phone/link with proper nested structures
- Text columns pass through as plain strings
Made-with: Cursor
This commit is contained in:
parent
a0f5022de2
commit
d7d859f49d
3 changed files with 84 additions and 27 deletions
|
|
@ -114,7 +114,7 @@ type SettingsData = {
|
|||
mondayApiToken: string;
|
||||
mondayBoardId: string;
|
||||
mondayEnabled: boolean;
|
||||
mondayColumnMap: Record<string, string> | null;
|
||||
mondayColumnMap: Record<string, unknown> | null;
|
||||
mondayWebhookId: string;
|
||||
mondayWebhookUrl: string;
|
||||
webhookUrl: string;
|
||||
|
|
@ -358,6 +358,14 @@ export default function SettingsPage() {
|
|||
const data = await res.json();
|
||||
if (res.ok && data.columns) {
|
||||
setMondayColumns(data.columns);
|
||||
const typeMap: Record<string, string> = {};
|
||||
for (const col of data.columns as MondayColumn[]) {
|
||||
typeMap[col.id] = col.type;
|
||||
}
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
mondayColumnMap: { ...(s.mondayColumnMap || {}), _columnTypes: typeMap },
|
||||
}));
|
||||
toast.success(`Found ${data.columns.length} columns`);
|
||||
} else {
|
||||
toast.error(data.error || "Failed to fetch columns");
|
||||
|
|
@ -945,7 +953,7 @@ export default function SettingsPage() {
|
|||
<div key={field} className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground w-36 shrink-0">{label}</span>
|
||||
<Select
|
||||
value={(settings.mondayColumnMap || {})[field] || "__none__"}
|
||||
value={String((settings.mondayColumnMap || {})[field] || "__none__")}
|
||||
onValueChange={(v) => setColumnMapping(field, !v || v === "__none__" ? "" : v)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
|
|
@ -963,7 +971,7 @@ export default function SettingsPage() {
|
|||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground w-36 shrink-0">Files (images)</span>
|
||||
<Select
|
||||
value={(settings.mondayColumnMap || {})._files || "__none__"}
|
||||
value={String((settings.mondayColumnMap || {})._files || "__none__")}
|
||||
onValueChange={(v) => setColumnMapping("_files", !v || v === "__none__" ? "" : v)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ async function handleMonday(
|
|||
} else if (event === "card_reviewed" || event === "card_exported") {
|
||||
const token = settings.mondayApiToken;
|
||||
const boardId = settings.mondayBoardId;
|
||||
const columnMap = (settings.mondayColumnMap as Record<string, string>) ?? {};
|
||||
const columnMap = (settings.mondayColumnMap as Record<string, unknown>) ?? {};
|
||||
const mondayItemId = card.mondayItemId as string | null;
|
||||
if (mondayItemId) {
|
||||
const columnValues = mapCardToColumnValues(card, columnMap);
|
||||
|
|
@ -198,7 +198,7 @@ export async function pushCardToMonday(
|
|||
const cardData = card as unknown as Record<string, unknown>;
|
||||
const token = settings.mondayApiToken;
|
||||
const boardId = settings.mondayBoardId;
|
||||
const columnMap = (settings.mondayColumnMap as Record<string, string>) ?? {};
|
||||
const columnMap = (settings.mondayColumnMap as Record<string, unknown>) ?? {};
|
||||
|
||||
const columnValues = mapCardToColumnValues(cardData, columnMap);
|
||||
const itemName = (card.name as string) || "Unnamed Card";
|
||||
|
|
@ -216,7 +216,7 @@ export async function pushCardToMonday(
|
|||
data: { mondayItemId: itemId },
|
||||
});
|
||||
|
||||
const filesColId = columnMap._files;
|
||||
const filesColId = columnMap._files as string | undefined;
|
||||
if (filesColId) {
|
||||
for (const imgPath of [cardData.backImagePath, cardData.frontImagePath]) {
|
||||
if (typeof imgPath === "string" && imgPath) {
|
||||
|
|
|
|||
|
|
@ -140,45 +140,93 @@ export async function readItem(
|
|||
};
|
||||
}
|
||||
|
||||
const BOOLEAN_CARD_FIELDS = new Set([
|
||||
"iSaidYesBookSent",
|
||||
"ftGuestLetterSent",
|
||||
"prayerForTeam",
|
||||
"prayerConfidential",
|
||||
]);
|
||||
|
||||
function formatValue(cardField: string, val: unknown): unknown | null {
|
||||
if (BOOLEAN_CARD_FIELDS.has(cardField) || typeof val === "boolean") {
|
||||
return { checked: val === true || val === "true" ? "true" : "false" };
|
||||
}
|
||||
|
||||
function toCleanString(val: unknown): string | null {
|
||||
if (val === null || val === undefined) return null;
|
||||
if (Array.isArray(val)) {
|
||||
const joined = val.filter(Boolean).join(", ");
|
||||
return joined || null;
|
||||
}
|
||||
|
||||
if (typeof val === "object") {
|
||||
const s = JSON.stringify(val);
|
||||
return s === "{}" || s === "[]" ? null : s;
|
||||
}
|
||||
const str = String(val).trim();
|
||||
if (str === "" || str === "null" || str === "undefined" || str === "[object Object]") return null;
|
||||
return str;
|
||||
}
|
||||
|
||||
const strVal = String(val).trim();
|
||||
if (strVal === "" || strVal === "null" || strVal === "undefined" || strVal === "[object Object]") return null;
|
||||
return strVal;
|
||||
function formatForColumnType(colType: string, val: unknown): unknown | null {
|
||||
switch (colType) {
|
||||
case "boolean": {
|
||||
const isChecked = val === true || val === "true";
|
||||
return { checked: isChecked ? "true" : "false" };
|
||||
}
|
||||
|
||||
case "color": {
|
||||
const str = toCleanString(val);
|
||||
return str ? { label: str } : null;
|
||||
}
|
||||
|
||||
case "dropdown": {
|
||||
const str = toCleanString(val);
|
||||
if (!str) return null;
|
||||
const labels = str.split(",").map((s) => s.trim()).filter(Boolean);
|
||||
return labels.length > 0 ? { labels } : null;
|
||||
}
|
||||
|
||||
case "date": {
|
||||
const str = toCleanString(val);
|
||||
if (!str) return null;
|
||||
const iso = parseToISO(str);
|
||||
return iso ? { date: iso } : str;
|
||||
}
|
||||
|
||||
case "email": {
|
||||
const str = toCleanString(val);
|
||||
return str ? { email: str, text: str } : null;
|
||||
}
|
||||
|
||||
case "phone": {
|
||||
const str = toCleanString(val);
|
||||
return str ? { phone: str, countryShortName: "US" } : null;
|
||||
}
|
||||
|
||||
case "link": {
|
||||
const str = toCleanString(val);
|
||||
return str ? { url: str, text: str } : null;
|
||||
}
|
||||
|
||||
default: {
|
||||
return toCleanString(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseToISO(dateStr: string): string | null {
|
||||
const parts = dateStr.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{2,4})$/);
|
||||
if (!parts) return null;
|
||||
let [, m, d, y] = parts;
|
||||
if (y.length === 2) y = parseInt(y) > 50 ? `19${y}` : `20${y}`;
|
||||
const month = m.padStart(2, "0");
|
||||
const day = d.padStart(2, "0");
|
||||
return `${y}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function mapCardToColumnValues(
|
||||
card: Record<string, unknown>,
|
||||
columnMap: Record<string, string>
|
||||
columnMap: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
const values: Record<string, unknown> = {};
|
||||
const columnTypes = ((columnMap._columnTypes as Record<string, string>) ?? {});
|
||||
|
||||
for (const [cardField, colId] of Object.entries(columnMap)) {
|
||||
if (!colId || !cardField || cardField.startsWith("_")) continue;
|
||||
for (const [cardField, rawColId] of Object.entries(columnMap)) {
|
||||
if (!rawColId || !cardField || cardField.startsWith("_") || typeof rawColId !== "string") continue;
|
||||
const colId = rawColId;
|
||||
const val = card[cardField];
|
||||
if (val === null || val === undefined) continue;
|
||||
|
||||
const formatted = formatValue(cardField, val);
|
||||
const colType = columnTypes[colId] || "text";
|
||||
const formatted = formatForColumnType(colType, val);
|
||||
if (formatted !== null) {
|
||||
values[colId] = formatted;
|
||||
}
|
||||
|
|
@ -189,10 +237,11 @@ export function mapCardToColumnValues(
|
|||
|
||||
export function mapItemToCardFields(
|
||||
columnValues: ItemColumnValue[],
|
||||
columnMap: Record<string, string>
|
||||
columnMap: Record<string, unknown>
|
||||
): Record<string, string> {
|
||||
const reverseMap: Record<string, string> = {};
|
||||
for (const [cardField, colId] of Object.entries(columnMap)) {
|
||||
if (cardField.startsWith("_") || typeof colId !== "string") continue;
|
||||
reverseMap[colId] = cardField;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue