diff --git a/app/page.tsx b/app/page.tsx index 2fc0452..6fcf44a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -13,19 +13,31 @@ export default async function Dashboard() { let errorMsg: string | null = null; try { - const [inv, sc, latestResp] = await Promise.all([ - pb.collection("JEI_invoices").getList(1, 1), + // NOTE: PB JS SDK auto-cancels concurrent requests to the same collection + // (see https://github.com/pocketbase/js-sdk#auto-cancellation). Fetch the + // invoice list ONCE and derive both "count" and "latest 5" from it. + const [invoicesResp, sc] = await Promise.all([ + pb.collection("JEI_invoices").getList(1, 5000), pb.collection("JEI_stock_codes").getList(1, 1), - pb.collection("JEI_invoices").getList(1, 5, { sort: "-invoice_date" }), ]); - invoiceCount = inv.totalItems; + invoiceCount = invoicesResp.totalItems; stockCodeCount = sc.totalItems; - latest = latestResp.items; - // Sum totals across all invoices - const all = await pb.collection("JEI_invoices").getList(1, 5000); - totalSpent = all.items.reduce((s: number, r: any) => s + (r.invoice_total || 0), 0); + // Latest 5 by invoice_date + const sorted = [...invoicesResp.items].sort((a: any, b: any) => + (b.invoice_date || "").localeCompare(a.invoice_date || "") + ); + latest = sorted.slice(0, 5); + totalSpent = invoicesResp.items.reduce((s: number, r: any) => s + (r.invoice_total || 0), 0); } catch (e: any) { - errorMsg = e?.message || String(e); + const msg = String(e?.message || e); + // Detect auto-cancellation and give a clearer hint + if (/autocancelled|auto-cancelled|aborted/i.test(msg)) { + errorMsg = "Dashboard query was auto-cancelled (concurrent same-collection calls). Refresh the page."; + } else if (/401|Unauthorized/i.test(msg)) { + errorMsg = "PB token expired — refresh in PB admin UI and update .env.local"; + } else { + errorMsg = msg; + } } return ( diff --git a/scripts/patch_collections.py b/scripts/patch_collections.py new file mode 100644 index 0000000..d2a948c --- /dev/null +++ b/scripts/patch_collections.py @@ -0,0 +1,122 @@ +""" +PATCH schema fields onto the JEI_* collections if they're missing. + +Workaround for a known PocketBase quirk where collections created via API +sometimes persist with empty schema. The rules PATCH trick works for empty +rules; for empty schema we explicitly PATCH the schema field. + +Run: python scripts/patch_collections.py +""" +import os +import sys +import json +import urllib.request +from urllib.error import HTTPError + +PB_URL = os.environ.get("PB_URL", "https://pb.agent.apphub.co.za") +PB_TOKEN = os.environ.get("PB_TOKEN", "") +if not PB_TOKEN: + env_path = os.path.join(os.path.dirname(__file__), "..", ".env.local") + if os.path.exists(env_path): + for line in open(env_path): + if line.startswith("PB_TOKEN="): + PB_TOKEN = line.strip().split("=", 1)[1] +if not PB_TOKEN: + sys.exit("ERROR: PB_TOKEN not set") +HEADERS = {"Authorization": PB_TOKEN, "Content-Type": "application/json"} + + +def pb_get(path): + req = urllib.request.Request(PB_URL + path, headers=HEADERS, method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()) + + +def pb_patch(path, body): + req = urllib.request.Request(PB_URL + path, data=json.dumps(body).encode("utf-8"), + headers=HEADERS, method="PATCH") + try: + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()), None + except HTTPError as e: + return None, e.read().decode() + + +# Mirror of create_collections.py schemas (kept independent for clarity) +SUPERUSER_RULES = {"listRule": "", "viewRule": "", "createRule": "", + "updateRule": "", "deleteRule": ""} + + +def invoices_schema(): + return [ + {"name": "invoice_no", "type": "text", "required": True, "options": {"min": 1, "max": 50, "pattern": ""}}, + {"name": "invoice_date", "type": "date", "required": True, "options": {"min": "", "max": ""}}, + {"name": "document_type", "type": "select", "required": True, "options": {"maxSelect": 1, "values": ["invoice", "proforma", "quote"]}}, + {"name": "customer_order_no", "type": "text", "required": False, "options": {"min": 0, "max": 100, "pattern": ""}}, + {"name": "buyer_name_variant", "type": "select", "required": True, "options": {"maxSelect": 1, "values": ["t/a JEI South Africa", "JEI South Africa", "JEI South Africa Importers"]}}, + {"name": "supplier_name", "type": "text", "required": True, "options": {"min": 0, "max": 200, "pattern": ""}}, + {"name": "account_ref", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, + {"name": "total_net", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "carriage_net", "type": "number", "required": False, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "invoice_total", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "deliver_to_name", "type": "text", "required": False, "options": {"min": 0, "max": 200, "pattern": ""}}, + {"name": "deliver_to_address", "type": "text", "required": False, "options": {"min": 0, "max": 1000, "pattern": ""}}, + {"name": "source_filename", "type": "text", "required": False, "options": {"min": 0, "max": 500, "pattern": ""}}, + {"name": "notes", "type": "text", "required": False, "options": {"min": 0, "max": 5000, "pattern": ""}}, + {"name": "pdf_file", "type": "file", "required": False, "options": {"maxSelect": 1, "maxSize": 0, "mimeTypes": ["application/pdf"], "thumbs": []}}, + ] + + +def stock_codes_schema(): + return [ + {"name": "code", "type": "text", "required": True, "options": {"min": 1, "max": 100, "pattern": ""}}, + {"name": "description_goods", "type": "text", "required": True, "options": {"min": 1, "max": 500, "pattern": ""}}, + {"name": "first_seen_date", "type": "date", "required": False, "options": {"min": "", "max": ""}}, + {"name": "last_seen_date", "type": "date", "required": False, "options": {"min": "", "max": ""}}, + ] + + +def lines_schema(): + # We need to resolve the relation target ids + cols = pb_get("/api/collections?perPage=200")["items"] + inv = next(c for c in cols if c["name"] == "JEI_invoices") + sc = next(c for c in cols if c["name"] == "JEI_stock_codes") + return [ + {"name": "invoice", "type": "relation", "required": True, "options": {"collectionId": inv["id"], "cascadeDelete": True, "minSelect": 1, "maxSelect": 1}}, + {"name": "line_no", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": True}}, + {"name": "stock_code", "type": "relation", "required": True, "options": {"collectionId": sc["id"], "cascadeDelete": True, "minSelect": 1, "maxSelect": 1}}, + {"name": "quantity", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "unit_price", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "net_amount", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, + {"name": "commodity_code", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, + {"name": "country_of_origin", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, + ] + + +PATCHES = [ + ("JEI_invoices", invoices_schema), + ("JEI_stock_codes", stock_codes_schema), + ("JEI_invoice_lines", lines_schema), +] + + +def main(): + cols = pb_get("/api/collections?perPage=200")["items"] + for name, factory in PATCHES: + col = next((c for c in cols if c["name"] == name), None) + if not col: + print(f"SKIP: {name} does not exist") + continue + if col.get("schema") and len(col["schema"]) > 0: + print(f"OK: {name} already has {len(col['schema'])} fields") + continue + body = {"schema": factory(), **SUPERUSER_RULES} + result, err = pb_patch(f"/api/collections/{col['id']}", body) + if err: + print(f"FAIL: {name} — {err}") + sys.exit(1) + print(f"PATCHED: {name} → {len(result.get('schema', []))} fields") + + +if __name__ == "__main__": + main() diff --git a/scripts/pb_snapshot.txt b/scripts/pb_snapshot.txt new file mode 100644 index 0000000..5b3274e --- /dev/null +++ b/scripts/pb_snapshot.txt @@ -0,0 +1,674 @@ +uid=1_0 RootWebArea "ysterbaard_annual_returns - Acme" url="https://pb.agent.apphub.co.za/_/#/collections?collection=ysterbaard_annual_returns" + uid=1_1 main + uid=2_0 link "App logo" url="https://pb.agent.apphub.co.za/_/#/" + uid=2_1 image "App logo" url="https://pb.agent.apphub.co.za/_/images/logo_white.svg" + uid=2_2 navigation + uid=2_3 link "Collections" url="https://pb.agent.apphub.co.za/_/#/collections" + uid=2_4 StaticText "Collections" + uid=2_5 link "Logs" url="https://pb.agent.apphub.co.za/_/#/logs" + uid=2_6 StaticText "Logs" + uid=2_7 link "Settings" url="https://pb.agent.apphub.co.za/_/#/settings" + uid=2_8 StaticText "Settings" + uid=2_9 button "Color scheme" expandable + uid=2_10 button "ysterbaard@apphub.co.za" expandable + uid=2_11 complementary + uid=2_12 textbox "Search collections..." + uid=2_13 button description="Collections overview" + uid=2_14 navigation + uid=2_15 DisclosureTriangle "Collections" expandable expanded + uid=2_16 button "users " + uid=2_17 button "" description="Pin" + uid=2_18 button "groups " + uid=2_19 button "" description="Pin" + uid=2_20 button "sable_api " + uid=2_21 button "" description="Pin" + uid=2_22 button "sable_notes " + uid=2_23 button "" description="Pin" + uid=2_24 button "tasks " + uid=2_25 button "" description="Pin" + uid=2_26 button "test_patch_schema " + uid=2_27 button "" description="Pin" + uid=2_28 button "ysterbaard_SARS_EMP201_PAYE " + uid=2_29 button "" description="Pin" + uid=2_30 button "ysterbaard_SARS_VAT_VAT201 " + uid=2_31 button "" description="Pin" + uid=2_32 button "ysterbaard_annual_returns " + uid=2_33 button "" description="Pin" + uid=2_34 button "ysterbaard_companies " + uid=2_35 button "" description="Pin" + uid=2_36 button "ysterbaard_company_directors " + uid=2_37 button "" description="Pin" + uid=2_38 button "ysterbaard_company_documents " + uid=2_39 button "" description="Pin" + uid=2_40 button "ysterbaard_company_emails " + uid=2_41 button "" description="Pin" + uid=2_42 button "ysterbaard_company_registrations " + uid=2_43 button "" description="Pin" + uid=2_44 button "ysterbaard_director_appointments " + uid=2_45 button "" description="Pin" + uid=2_46 button "ysterbaard_director_documents " + uid=2_47 button "" description="Pin" + uid=2_48 button "ysterbaard_director_resignations " + uid=2_49 button "" description="Pin" + uid=2_50 button "ysterbaard_directors " + uid=2_51 button "" description="Pin" + uid=2_52 button "ysterbaard_document_types " + uid=2_53 button "" description="Pin" + uid=2_54 button "ysterbaard_notes " + uid=2_55 button "" description="Pin" + uid=2_56 button "ysterbaard_onboarding_submissions " + uid=2_57 button "" description="Pin" + uid=2_58 button "ysterbaard_reg_directors " + uid=2_59 button "" description="Pin" + uid=2_60 button "ysterbaard_reg_shareholders " + uid=2_61 button "" description="Pin" + uid=2_62 button "ysterbaard_registration_emails " + uid=2_63 button "" description="Pin" + uid=2_64 button "ysterbaard_resolution_attendees " + uid=2_65 button "" description="Pin" + uid=2_66 button "ysterbaard_resolution_received_letters " + uid=2_67 button "" description="Pin" + uid=2_68 button "ysterbaard_resolution_signers " + uid=2_69 button "" description="Pin" + uid=2_70 button "ysterbaard_resolutions " + uid=2_71 button "" description="Pin" + uid=2_72 button "ysterbaard_sars_irp6_income_tax " + uid=2_73 button "" description="Pin" + uid=2_74 DisclosureTriangle " System" expandable + uid=2_75 button "New collection" + uid=2_76 navigation + uid=2_77 StaticText "Collections" + uid=1_5 StaticText "/" + uid=2_78 StaticText "ysterbaard_annual_returns" + uid=2_79 button "Collection settings" + uid=2_80 button "Refresh" + uid=2_81 button "API preview" + uid=2_82 button "New record" + uid=2_83 form + uid=2_84 button "Search history" expandable + uid=2_85 textbox multiline + uid=2_86 checkbox "" + uid=1_5 StaticText "" + uid=2_87 StaticText "id" + uid=2_88 StaticText "company" + uid=2_89 StaticText "ar_year" + uid=2_90 StaticText "ar_month" + uid=2_91 StaticText "ar_non_compliance_date" + uid=2_92 StaticText "is_filed" + uid=2_93 StaticText "amount_paid" + uid=2_94 StaticText "date_filed" + uid=2_95 StaticText "source" + uid=2_96 button "Toggle columns" expandable + uid=2_97 row + uid=2_98 checkbox "" + uid=1_5 StaticText "" + uid=2_99 button "Copy" + uid=2_100 StaticText "vzusowawsnvryc4c4q5ib" + uid=2_101 StaticText "True" + uid=2_102 StaticText "2026" + uid=2_103 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_104 StaticText "False" + uid=2_105 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_106 StaticText "manual" + uid=2_107 row + uid=2_108 checkbox "" + uid=1_5 StaticText "" + uid=2_109 button "Copy" + uid=2_110 StaticText "utbehhh45ivqxsr3stzg4" + uid=2_111 StaticText "True" + uid=2_112 StaticText "2025" + uid=2_113 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_114 StaticText "False" + uid=2_115 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_116 StaticText "manual" + uid=2_117 row + uid=2_118 checkbox "" + uid=1_5 StaticText "" + uid=2_119 button "Copy" + uid=2_120 StaticText "lxsgxus2d2earitha3o8r" + uid=2_121 StaticText "True" + uid=2_122 StaticText "2024" + uid=2_123 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_124 StaticText "False" + uid=2_125 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_126 StaticText "manual" + uid=2_127 row + uid=2_128 checkbox "" + uid=1_5 StaticText "" + uid=2_129 button "Copy" + uid=2_130 StaticText "vp7e1adhqtc30qc8ucojn" + uid=2_131 StaticText "True" + uid=2_132 StaticText "2023" + uid=2_133 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_134 StaticText "False" + uid=2_135 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_136 StaticText "manual" + uid=2_137 row + uid=2_138 checkbox "" + uid=1_5 StaticText "" + uid=2_139 button "Copy" + uid=2_140 StaticText "qrplpjb1s3jcsmv05lfn6" + uid=2_141 StaticText "True" + uid=2_142 StaticText "2022" + uid=2_143 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_144 StaticText "False" + uid=2_145 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_146 StaticText "manual" + uid=2_147 row + uid=2_148 checkbox "" + uid=1_5 StaticText "" + uid=2_149 button "Copy" + uid=2_150 StaticText "mvjd01oe6rh6khh4fbmvo" + uid=2_151 StaticText "True" + uid=2_152 StaticText "2021" + uid=2_153 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_154 StaticText "False" + uid=2_155 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_156 StaticText "manual" + uid=2_157 row + uid=2_158 checkbox "" + uid=1_5 StaticText "" + uid=2_159 button "Copy" + uid=2_160 StaticText "3f1a3miti26r4elhcvkkx" + uid=2_161 StaticText "True" + uid=2_162 StaticText "2020" + uid=2_163 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_164 StaticText "False" + uid=2_165 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_166 StaticText "manual" + uid=2_167 row + uid=2_168 checkbox "" + uid=1_5 StaticText "" + uid=2_169 button "Copy" + uid=2_170 StaticText "x93s1dx044zjm6v5dgaty" + uid=2_171 StaticText "True" + uid=2_172 StaticText "2026" + uid=2_173 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_174 StaticText "False" + uid=2_175 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_176 StaticText "manual" + uid=2_177 row + uid=2_178 checkbox "" + uid=1_5 StaticText "" + uid=2_179 button "Copy" + uid=2_180 StaticText "r1elimr6oe4jbmi7mj4o4" + uid=2_181 StaticText "True" + uid=2_182 StaticText "2025" + uid=2_183 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_184 StaticText "False" + uid=2_185 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_186 StaticText "manual" + uid=2_187 row + uid=2_188 checkbox "" + uid=1_5 StaticText "" + uid=2_189 button "Copy" + uid=2_190 StaticText "1m56e3az8hbbtympbgyyv" + uid=2_191 StaticText "True" + uid=2_192 StaticText "2024" + uid=2_193 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_194 StaticText "False" + uid=2_195 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_196 StaticText "manual" + uid=2_197 row + uid=2_198 checkbox "" + uid=1_5 StaticText "" + uid=2_199 button "Copy" + uid=2_200 StaticText "eyjrz9r5o9vvyad651e93" + uid=2_201 StaticText "True" + uid=2_202 StaticText "2023" + uid=2_203 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_204 StaticText "False" + uid=2_205 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_206 StaticText "manual" + uid=2_207 row + uid=2_208 checkbox "" + uid=1_5 StaticText "" + uid=2_209 button "Copy" + uid=2_210 StaticText "oqs26zkb963yrxwci7uty" + uid=2_211 StaticText "True" + uid=2_212 StaticText "2022" + uid=2_213 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_214 StaticText "False" + uid=2_215 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_216 StaticText "manual" + uid=2_217 row + uid=2_218 checkbox "" + uid=1_5 StaticText "" + uid=2_219 button "Copy" + uid=2_220 StaticText "g57qpwq4mk11y0iox96gd" + uid=2_221 StaticText "True" + uid=2_222 StaticText "2021" + uid=2_223 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_224 StaticText "False" + uid=2_225 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_226 StaticText "manual" + uid=2_227 row + uid=2_228 checkbox "" + uid=1_5 StaticText "" + uid=2_229 button "Copy" + uid=2_230 StaticText "newgkeoeixf2tn3ddsm4o" + uid=2_231 StaticText "True" + uid=2_232 StaticText "2020" + uid=2_233 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_234 StaticText "False" + uid=2_235 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_236 StaticText "manual" + uid=2_237 row + uid=2_238 checkbox "" + uid=1_5 StaticText "" + uid=2_239 button "Copy" + uid=2_240 StaticText "px32hbbepgd8g4k3x6jw2" + uid=2_241 StaticText "True" + uid=2_242 StaticText "2026" + uid=2_243 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_244 StaticText "False" + uid=2_245 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_246 StaticText "manual" + uid=2_247 row + uid=2_248 checkbox "" + uid=1_5 StaticText "" + uid=2_249 button "Copy" + uid=2_250 StaticText "e9dfpce06qbnszabfagnb" + uid=2_251 StaticText "True" + uid=2_252 StaticText "2025" + uid=2_253 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_254 StaticText "False" + uid=2_255 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_256 StaticText "manual" + uid=2_257 row + uid=2_258 checkbox "" + uid=1_5 StaticText "" + uid=2_259 button "Copy" + uid=2_260 StaticText "rt0i5vcyefxz9a8b82q0s" + uid=2_261 StaticText "True" + uid=2_262 StaticText "2024" + uid=2_263 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_264 StaticText "False" + uid=2_265 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_266 StaticText "manual" + uid=2_267 row + uid=2_268 checkbox "" + uid=1_5 StaticText "" + uid=2_269 button "Copy" + uid=2_270 StaticText "zj57br33bmi80fkktefoi" + uid=2_271 StaticText "True" + uid=2_272 StaticText "2023" + uid=2_273 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_274 StaticText "False" + uid=2_275 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_276 StaticText "manual" + uid=2_277 row + uid=2_278 checkbox "" + uid=1_5 StaticText "" + uid=2_279 button "Copy" + uid=2_280 StaticText "ckrqqcb2osh1n6ad053y5" + uid=2_281 StaticText "True" + uid=2_282 StaticText "2022" + uid=2_283 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_284 StaticText "False" + uid=2_285 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_286 StaticText "manual" + uid=2_287 row + uid=2_288 checkbox "" + uid=1_5 StaticText "" + uid=2_289 button "Copy" + uid=2_290 StaticText "05y3njez84fa17mt263pu" + uid=2_291 StaticText "True" + uid=2_292 StaticText "2021" + uid=2_293 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_294 StaticText "False" + uid=2_295 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_296 StaticText "manual" + uid=2_297 row + uid=2_298 checkbox "" + uid=1_5 StaticText "" + uid=2_299 button "Copy" + uid=2_300 StaticText "aw4zn92g86sr0v24xtcmu" + uid=2_301 StaticText "True" + uid=2_302 StaticText "2020" + uid=2_303 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_304 StaticText "False" + uid=2_305 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_306 StaticText "manual" + uid=2_307 row + uid=2_308 checkbox "" + uid=1_5 StaticText "" + uid=2_309 button "Copy" + uid=2_310 StaticText "hh4r8l6vgvcvq3d42xe4y" + uid=2_311 StaticText "True" + uid=2_312 StaticText "2026" + uid=2_313 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_314 StaticText "False" + uid=2_315 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_316 StaticText "bizportal" + uid=2_317 row + uid=2_318 checkbox "" + uid=1_5 StaticText "" + uid=2_319 button "Copy" + uid=2_320 StaticText "aro8cui2k9x7y2nhuiou3" + uid=2_321 StaticText "True" + uid=2_322 StaticText "2025" + uid=2_323 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_324 StaticText "False" + uid=2_325 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_326 StaticText "bizportal" + uid=2_327 row + uid=2_328 checkbox "" + uid=1_5 StaticText "" + uid=2_329 button "Copy" + uid=2_330 StaticText "nmu5nsrdytqsl1p2j51d3" + uid=2_331 StaticText "True" + uid=2_332 StaticText "2024" + uid=2_333 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_334 StaticText "False" + uid=2_335 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_336 StaticText "bizportal" + uid=2_337 row + uid=2_338 checkbox "" + uid=1_5 StaticText "" + uid=2_339 button "Copy" + uid=2_340 StaticText "7m4qh6vfdn6yknzyia90f" + uid=2_341 StaticText "True" + uid=2_342 StaticText "2023" + uid=2_343 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_344 StaticText "False" + uid=2_345 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_346 StaticText "bizportal" + uid=2_347 row + uid=2_348 checkbox "" + uid=1_5 StaticText "" + uid=2_349 button "Copy" + uid=2_350 StaticText "gdk19i23qb89onuwvcgso" + uid=2_351 StaticText "True" + uid=2_352 StaticText "2022" + uid=2_353 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_354 StaticText "False" + uid=2_355 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_356 StaticText "bizportal" + uid=2_357 row + uid=2_358 checkbox "" + uid=1_5 StaticText "" + uid=2_359 button "Copy" + uid=2_360 StaticText "kod1zxa0dx1jc870avlkz" + uid=2_361 StaticText "True" + uid=2_362 StaticText "2021" + uid=2_363 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_364 StaticText "False" + uid=2_365 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_366 StaticText "bizportal" + uid=2_367 row + uid=2_368 checkbox "" + uid=1_5 StaticText "" + uid=2_369 button "Copy" + uid=2_370 StaticText "e9bmkdv2z0jel4w5cv9qa" + uid=2_371 StaticText "True" + uid=2_372 StaticText "2020" + uid=2_373 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_374 StaticText "False" + uid=2_375 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_376 StaticText "bizportal" + uid=2_377 row + uid=2_378 checkbox "" + uid=1_5 StaticText "" + uid=2_379 button "Copy" + uid=2_380 StaticText "2v2nos72ysckf6q6awkn5" + uid=2_381 StaticText "True" + uid=2_382 StaticText "2026" + uid=2_383 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_384 StaticText "False" + uid=2_385 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_386 StaticText "manual" + uid=2_387 row + uid=2_388 checkbox "" + uid=1_5 StaticText "" + uid=2_389 button "Copy" + uid=2_390 StaticText "usodm5otthx7d2wro8bwx" + uid=2_391 StaticText "True" + uid=2_392 StaticText "2025" + uid=2_393 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_394 StaticText "False" + uid=2_395 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_396 StaticText "manual" + uid=2_397 row + uid=2_398 checkbox "" + uid=1_5 StaticText "" + uid=2_399 button "Copy" + uid=2_400 StaticText "euf62r18bbgdn37aw4jrl" + uid=2_401 StaticText "True" + uid=2_402 StaticText "2024" + uid=2_403 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_404 StaticText "False" + uid=2_405 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_406 StaticText "manual" + uid=2_407 row + uid=2_408 checkbox "" + uid=1_5 StaticText "" + uid=2_409 button "Copy" + uid=2_410 StaticText "ii73b9ovz2pq5ug9pdt5b" + uid=2_411 StaticText "True" + uid=2_412 StaticText "2023" + uid=2_413 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_414 StaticText "False" + uid=2_415 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_416 StaticText "manual" + uid=2_417 row + uid=2_418 checkbox "" + uid=1_5 StaticText "" + uid=2_419 button "Copy" + uid=2_420 StaticText "auf2h2a96of5xw5hnmf2n" + uid=2_421 StaticText "True" + uid=2_422 StaticText "2022" + uid=2_423 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_424 StaticText "False" + uid=2_425 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_426 StaticText "manual" + uid=2_427 row + uid=2_428 checkbox "" + uid=1_5 StaticText "" + uid=2_429 button "Copy" + uid=2_430 StaticText "amcbd5kbuc3bmw4m2qdiv" + uid=2_431 StaticText "True" + uid=2_432 StaticText "2021" + uid=2_433 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_434 StaticText "False" + uid=2_435 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_436 StaticText "manual" + uid=2_437 row + uid=2_438 checkbox "" + uid=1_5 StaticText "" + uid=2_439 button "Copy" + uid=2_440 StaticText "v0qye9ln762yfdlq7d4ch" + uid=2_441 StaticText "True" + uid=2_442 StaticText "2020" + uid=2_443 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_444 StaticText "False" + uid=2_445 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_446 StaticText "manual" + uid=2_447 row + uid=2_448 checkbox "" + uid=1_5 StaticText "" + uid=2_449 button "Copy" + uid=2_450 StaticText "k3pbizpi0iaymplwy0fa1" + uid=2_451 StaticText "True" + uid=2_452 StaticText "2026" + uid=2_453 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_454 StaticText "False" + uid=2_455 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_456 StaticText "manual" + uid=2_457 row + uid=2_458 checkbox "" + uid=1_5 StaticText "" + uid=2_459 button "Copy" + uid=2_460 StaticText "bqd0glsquey9b7z4w2k50" + uid=2_461 StaticText "True" + uid=2_462 StaticText "2025" + uid=2_463 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_464 StaticText "False" + uid=2_465 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_466 StaticText "manual" + uid=2_467 row + uid=2_468 checkbox "" + uid=1_5 StaticText "" + uid=2_469 button "Copy" + uid=2_470 StaticText "xx9zhklos3e90w7opej5s" + uid=2_471 StaticText "True" + uid=2_472 StaticText "2024" + uid=2_473 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_474 StaticText "False" + uid=2_475 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_476 StaticText "manual" + uid=2_477 row + uid=2_478 checkbox "" + uid=1_5 StaticText "" + uid=2_479 button "Copy" + uid=2_480 StaticText "p4wn8dtp9qi1jh87gtt9g" + uid=2_481 StaticText "True" + uid=2_482 StaticText "2023" + uid=2_483 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_484 StaticText "False" + uid=2_485 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_486 StaticText "manual" + uid=2_487 row + uid=2_488 checkbox "" + uid=1_5 StaticText "" + uid=2_489 button "Copy" + uid=2_490 StaticText "3dj84c5u39hrv39ak8f7x" + uid=2_491 StaticText "True" + uid=2_492 StaticText "2022" + uid=2_493 StaticText "JANUARY" + uid=1_5 StaticText "N/A" + uid=2_494 StaticText "False" + uid=2_495 StaticText "0" + uid=1_5 StaticText "N/A" + uid=2_496 StaticText "manual" + uid=2_497 button "Load more" + uid=2_498 StaticText "Total: " + uid=2_499 StaticText "449" + uid=2_500 link "Docs" url="https://pocketbase.io/docs" + uid=2_501 StaticText "Docs" + uid=2_502 link "PocketBase v0.39.8" url="https://github.com/pocketbase/pocketbase/releases" + uid=2_503 StaticText "PocketBase v0.39.8" + uid=3_0 generic + uid=3_1 banner + uid=3_2 heading "Create collection" level="6" + uid=3_3 StaticText "Name" + uid=1_5 StaticText "*" + uid=3_4 textbox "Name *" required value="JEI_stock_codes" + uid=3_5 button "Type: Base" expandable + uid=3_6 navigation + uid=3_7 button "Fields" + uid=3_8 button "API rules" + uid=5_0 DisclosureTriangleGrouped "Field options" expandable + uid=1_5 StaticText "" + uid=5_1 banner + uid=1_5 StaticText "" + uid=5_2 textbox "" disableable disabled required value="id" + uid=5_3 StaticText "Required" + uid=5_4 button "Field options" + uid=5_5 DisclosureTriangleGrouped "Field options" expandable + uid=1_5 StaticText "" + uid=5_6 banner + uid=1_5 StaticText "" + uid=5_7 textbox "" focusable focused required value="text" + uid=5_8 button "Field options" + uid=5_9 DisclosureTriangleGrouped "Field options" expandable + uid=1_5 StaticText "" + uid=5_10 banner + uid=1_5 StaticText "" + uid=5_11 textbox "" required value="created" + uid=5_12 button "Create " expandable + uid=5_13 button "Field options" + uid=5_14 DisclosureTriangleGrouped "Field options" expandable + uid=1_5 StaticText "" + uid=5_15 banner + uid=1_5 StaticText "" + uid=5_16 textbox "" required value="updated" + uid=5_17 button "Create/Update " expandable + uid=5_18 button "Field options" + uid=3_24 button "New field" expandable + uid=3_25 StaticText "Unique constraints and indexes (" + uid=4_13 StaticText "0" + uid=3_27 StaticText ")" + uid=3_28 button "New index" + uid=3_29 contentinfo + uid=3_30 button "Close" + uid=3_31 button "Create" + uid=3_32 button "Save options" expandable diff --git a/scripts/test_pb_sdk.js b/scripts/test_pb_sdk.js new file mode 100644 index 0000000..99f36e1 --- /dev/null +++ b/scripts/test_pb_sdk.js @@ -0,0 +1,26 @@ +const { default: PocketBase } = require('pocketbase'); +const fs = require('fs'); +const envFile = fs.readFileSync('.env.local', 'utf8'); +const token = envFile.match(/PB_TOKEN=(.+)/)[1].trim(); +const url = envFile.match(/PB_URL=(.+)/)[1].trim(); +const pb = new PocketBase(url); +pb.authStore.save(token); + +(async () => { + try { + const col = await pb.collections.create({ + name: 'JEI_test_sdk', + type: 'base', + fields: [ + { name: 'foo', type: 'select', required: false, options: { maxSelect: 1, values: ['a','b'] } } + ], + listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' + }); + console.log('Created id=' + col.id + ' fields=' + col.fields.length); + await pb.collections.delete(col.id); + console.log('deleted'); + } catch (e) { + console.log('ERR: ' + e.message); + if (e.data) console.log('data: ' + JSON.stringify(e.data)); + } +})(); diff --git a/scripts/test_pb_select.js b/scripts/test_pb_select.js new file mode 100644 index 0000000..89d1a38 --- /dev/null +++ b/scripts/test_pb_select.js @@ -0,0 +1,60 @@ +// Test creating a select field with various formats to find what works +const { default: PocketBase } = require('pocketbase'); +const fs = require('fs'); + +const envFile = fs.readFileSync('.env.local', 'utf8'); +const token = envFile.match(/PB_TOKEN=(.+)/)[1].trim(); +const url = envFile.match(/PB_URL=(.+)/)[1].trim(); +const pb = new PocketBase(url); +pb.authStore.save(token); + +async function test(name, fields) { + try { + const col = await pb.collections.create({ + name: 'JEI_t_' + Date.now() + '_' + name, + type: 'base', + fields, + listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' + }); + console.log(`OK [${name}]: id=${col.id} fields=${col.fields.length}`); + // Get it back + const got = await pb.collections.getOne(col.id); + console.log(` fields detail:`); + got.fields.forEach(f => console.log(` - ${f.name} (${f.type}) opts=${JSON.stringify(f.options || {})}`)); + // Cleanup + await pb.collections.delete(col.id); + return true; + } catch (e) { + console.log(`FAIL [${name}]: ${e.message}`); + if (e.data) console.log(' data: ' + JSON.stringify(e.data)); + return false; + } +} + +(async () => { + // Test 1: select with explicit id + system:false + values only + await test('sel_basic', [ + { id: 'select_aaa', name: 'foo', type: 'select', system: false, required: true, options: { maxSelect: 1, values: ['a','b'] } } + ]); + + // Test 2: select with maxSelect as string + await test('sel_str', [ + { name: 'foo', type: 'select', options: { values: ['a','b'] } } + ]); + + // Test 3: multi-select + await test('sel_multi', [ + { name: 'foo', type: 'select', options: { maxSelect: 2, values: ['a','b','c'] } } + ]); + + // Test 4: text first then select + await test('text_then_sel', [ + { name: 'bar', type: 'text' }, + { name: 'foo', type: 'select', options: { maxSelect: 1, values: ['a','b'] } } + ]); + + // Test 5: select with no maxSelect at all + await test('sel_no_max', [ + { name: 'foo', type: 'select', required: false, options: { values: ['a','b'] } } + ]); +})();