@fayz-ai/plugin-support 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/HelpdeskConsole-7W5IAQIU.js +355 -0
- package/dist/HelpdeskConsole-7W5IAQIU.js.map +1 -0
- package/dist/SupportCenter-Z2HEPJGD.js +1090 -0
- package/dist/SupportCenter-Z2HEPJGD.js.map +1 -0
- package/dist/chunk-5K7VA5VP.js +106 -0
- package/dist/chunk-5K7VA5VP.js.map +1 -0
- package/dist/chunk-7NVBUXTX.js +300 -0
- package/dist/chunk-7NVBUXTX.js.map +1 -0
- package/dist/chunk-JJPB2T3Z.js +131 -0
- package/dist/chunk-JJPB2T3Z.js.map +1 -0
- package/dist/components/HelpdeskStatusPanel.d.ts +12 -0
- package/dist/components/HelpdeskStatusPanel.d.ts.map +1 -0
- package/dist/data/api.d.ts +15 -0
- package/dist/data/api.d.ts.map +1 -0
- package/dist/data/console.d.ts +37 -0
- package/dist/data/console.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +901 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/content.d.ts +24 -0
- package/dist/lib/content.d.ts.map +1 -0
- package/dist/lib/fix-prompt.d.ts +30 -0
- package/dist/lib/fix-prompt.d.ts.map +1 -0
- package/dist/lib/report-tool.d.ts +25 -0
- package/dist/lib/report-tool.d.ts.map +1 -0
- package/dist/lib/vocabulary.d.ts +34 -0
- package/dist/lib/vocabulary.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +9 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/store.d.ts +27 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/HelpdeskConsole.d.ts +10 -0
- package/dist/views/HelpdeskConsole.d.ts.map +1 -0
- package/dist/views/ScreenCaptureEditor.d.ts +12 -0
- package/dist/views/ScreenCaptureEditor.d.ts.map +1 -0
- package/dist/views/SupportCenter.d.ts +14 -0
- package/dist/views/SupportCenter.d.ts.map +1 -0
- package/dist/views/SupportMenu.d.ts +12 -0
- package/dist/views/SupportMenu.d.ts.map +1 -0
- package/dist/views/SupportRequestDetail.d.ts +6 -0
- package/dist/views/SupportRequestDetail.d.ts.map +1 -0
- package/dist/views/SupportRequestForm.d.ts +12 -0
- package/dist/views/SupportRequestForm.d.ts.map +1 -0
- package/dist/views/SupportRequestsPanel.d.ts +11 -0
- package/dist/views/SupportRequestsPanel.d.ts.map +1 -0
- package/dist/views/TicketDetailPage.d.ts +12 -0
- package/dist/views/TicketDetailPage.d.ts.map +1 -0
- package/package.json +56 -3
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/migrations/index.ts","../src/lib/report-tool.ts","../src/components/HelpdeskStatusPanel.tsx","../src/index.ts"],"names":["React"],"mappings":";;;;;;;;;;;AAAO,IAAM,EAAA,GAA6B;AAAA,EACxC,eAAA,EAAiB,SAAA;AAAA,EACjB,sBAAA,EAAwB,aAAA;AAAA,EACxB,sBAAA,EAAwB,SAAA;AAAA,EACxB,qBAAA,EAAuB;AACzB,CAAA;;;ACLO,IAAM,IAAA,GAA+B;AAAA,EAC1C,eAAA,EAAiB,SAAA;AAAA,EACjB,sBAAA,EAAwB,2BAAA;AAAA,EACxB,sBAAA,EAAwB,SAAA;AAAA,EACxB,qBAAA,EAAuB;AACzB,CAAA;;;ACFO,IAAM,cAAA,GAAyD;AAAA,EACpE,EAAA;AAAA,EACA,OAAA,EAAS;AACX,CAAA;;;ACFO,IAAM,sBAAA,GAAyB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAoN/B,IAAM,sBAAA,GAAyB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAkQ/B,IAAM,sCAAA,GAAyC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAiB/C,IAAM,0CAAA,GAA6C,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAmCnD,IAAM,UAAA,GAAiD;AAAA,EAC5D,EAAE,EAAA,EAAI,cAAA,EAAgB,GAAA,EAAK,sBAAA,EAAuB;AAAA,EAClD,EAAE,EAAA,EAAI,cAAA,EAAgB,GAAA,EAAK,sBAAA,EAAuB;AAAA,EAClD,EAAE,EAAA,EAAI,8BAAA,EAAgC,GAAA,EAAK,sCAAA,EAAuC;AAAA,EAClF,EAAE,EAAA,EAAI,kCAAA,EAAoC,GAAA,EAAK,0CAAA;AACjD,CAAA;AC3gBA,IAAI,mBAAA,GAA6C,IAAA;AAiBjD,SAAS,cAAc,GAAA,EAA2B;AAChD,EAAA,MAAM,IAAI,MAAA,CAAO,GAAA,IAAO,EAAE,CAAA,CAAE,IAAA,GAAO,WAAA,EAAY;AAC/C,EAAA,IAAI,CAAC,aAAA,EAAe,UAAA,EAAY,UAAA,EAAY,eAAY,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,GAAG,OAAO,aAAA;AACzG,EAAA,IAAI,CAAC,OAAA,EAAS,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,eAAe,mBAAA,EAAe,SAAS,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAC,CAAC,GAAG,OAAO,OAAA;AAEtH,EAAA,OAAO,SAAA;AACT;AAIA,SAAS,aAAA,CAAc,QAAQ,EAAA,EAA4C;AACzE,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,QAAA,EAAS,CAAE,KAAA;AAC5C,IAAA,OAAO,MAAM,KAAA,CAAM,CAAC,KAAK,CAAA,CAAE,SAAQ,CAAE,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAM,CAAA,CAAE,MAAM,KAAA,EAAO,CAAA,CAAE,OAAM,CAAE,CAAA;AAAA,EACpF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAGA,SAAS,YAAA,GAA8B;AACrC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,IAAA;AAC5C,EAAA,OAAO,QAAA,CAAS,IAAA,IAAQ,QAAA,CAAS,QAAA,IAAY,IAAA;AAC/C;AAEO,IAAM,cAAA,GAAiC;AAAA,EAC5C;AAAA,IACE,EAAA,EAAI,sBAAA;AAAA,IACJ,IAAA,EAAM,aAAA;AAAA,IACN,WAAA,EACE,CAAA,opCAAA,CAAA;AAAA,IAKF,IAAA,EAAM,UAAA;AAAA,IACN,IAAA,EAAM,SAAA;AAAA;AAAA;AAAA,IAGN,oBAAA,EAAsB,KAAA;AAAA,IACtB,UAAA,EAAY;AAAA,MACV,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,QAAA;AAAA,UACN,IAAA,EAAM,CAAC,SAAA,EAAW,aAAA,EAAe,OAAO,CAAA;AAAA,UACxC,WAAA,EAAa;AAAA,SACf;AAAA,QACA,WAAA,EAAa;AAAA,UACX,IAAA,EAAM,QAAA;AAAA,UACN,WAAA,EAAa;AAAA,SACf;AAAA,QACA,OAAA,EAAS;AAAA,UACP,IAAA,EAAM,QAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACf,OACF;AAAA,MACA,QAAA,EAAU,CAAC,aAAa;AAAA,KAC1B;AAAA,IACA,QAAA,EAAU,SAAA;AAAA,IACV,WAAA,EAAa;AAAA,MACX,EAAE,KAAA,EAAO,4BAAA,EAA8B,MAAA,EAAQ,4BAAA,EAA6B;AAAA,MAC5E,EAAE,KAAA,EAAO,4BAAA,EAA8B,MAAA,EAAQ,4BAAA,EAA6B;AAAA,MAC5E,EAAE,KAAA,EAAO,qBAAA,EAAoB,MAAA,EAAQ,qBAAA,EAAmB;AAAA,MACxD,EAAE,KAAA,EAAO,yBAAA,EAAqB,MAAA,EAAQ,6CAAA;AAAwC;AAChF,GACF;AAAA,EACA;AAAA,IACE,EAAA,EAAI,uBAAA;AAAA,IACJ,IAAA,EAAM,qBAAA;AAAA,IACN,WAAA,EACE,4aAAA;AAAA,IAIF,IAAA,EAAM,QAAA;AAAA,IACN,IAAA,EAAM,MAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,sGAAA;AAA2F;AACnI,KACF;AAAA,IACA,QAAA,EAAU;AAAA;AAEd,CAAA;AAiBA,eAAe,mBAAmB,IAAA,EAA2D;AAC3F,EAAA,MAAM,cAAc,MAAA,CAAO,IAAA,CAAK,WAAA,IAAe,EAAE,EAAE,IAAA,EAAK;AACxD,EAAA,IAAI,WAAA,CAAY,SAAS,CAAA,EAAG;AAC1B,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,qBAAA;AAAA,MACP,OAAA,EAAS;AAAA,KACX;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,IAAA,CAAK,IAAI,CAAA;AACpC,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,CAAK,OAAA,KAAY,QAAA,IAAY,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAK,GAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAK,GAAI,IAAA;AAChG,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc;AAAA,MAC9B,IAAA;AAAA,MACA,WAAA;AAAA,MACA,OAAA;AAAA,MACA,OAAO,YAAA,EAAa;AAAA;AAAA;AAAA;AAAA,MAIpB,cAAA,EAAgB,EAAE,QAAA,EAAU,iBAAA,CAAkB,EAAE,CAAA,EAAG,aAAA,EAAe,eAAc;AAAE,KACnF,CAAA;AAGD,IAAA,IAAI,QAAA,GAAW,CAAA;AACf,IAAA,MAAM,KAAA,GAAQ,mBAAA,GAAsB,mBAAA,EAAoB,GAAI,EAAC;AAC7D,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,OAAA,GAA6B;AAAA,QACjC,EAAA,EAAI,OAAO,UAAA,EAAW;AAAA,QACtB,IAAA;AAAA,QACA,IAAA,EAAM,KAAK,IAAA,IAAQ,OAAA;AAAA,QACnB,WAAA,EAAa,KAAK,IAAA,IAAQ,0BAAA;AAAA,QAC1B,WAAW,IAAA,CAAK;AAAA,OAClB;AACA,MAAA,IAAI;AACF,QAAA,MAAM,gBAAA,CAAiB,GAAA,CAAI,EAAA,EAAI,OAAO,CAAA;AACtC,QAAA,QAAA,IAAY,CAAA;AAAA,MACd,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAIA,IAAA,IAAI;AACF,MAAA,KAAK,eAAA,CAAgB,QAAA,EAAS,CAAE,QAAA,EAAS;AAAA,IAC3C,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,IAAA;AAAA,MACJ,IAAI,GAAA,CAAI,EAAA;AAAA,MACR,QAAQ,GAAA,CAAI,MAAA;AAAA,MACZ,MAAA,EAAQ,UAAA;AAAA,MACR,WAAA,EAAa,QAAA;AAAA,MACb,GAAA,EAAK,EAAE,EAAA,EAAI,GAAA,CAAI,IAAI,KAAA,EAAO,CAAA,mBAAA,EAAgB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,IAAA,EAAM,CAAA,gBAAA,EAAmB,GAAA,CAAI,EAAE,CAAA,CAAA,EAAG;AAAA,MAC1F,OAAA,EACE,sBAAgB,GAAA,CAAI,MAAM,cAAc,QAAA,GAAW,CAAA,KAAA,EAAQ,QAAQ,CAAA,SAAA,CAAA,GAAc,EAAE,CAAA,wIAAA;AAAA,KAGvF;AAAA,EACF,SAAS,KAAA,EAAO;AACd,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,cAAA;AAAA,MACP,OAAA,EAAS,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KACpD;AAAA,EACF;AACF;AAIA,SAAS,OAAO,MAAA,EAAyB;AACvC,EAAA,OAAO,MAAA,KAAW,YAAY,MAAA,KAAW,WAAA;AAC3C;AAIA,eAAe,oBAAoB,IAAA,EAAiD;AAClF,EAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,CAAK,KAAA,IAAS,EAAE,CAAA,CAAE,IAAA,GAAO,WAAA,EAAY;AAC1D,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,iBAAiB,CAAA,CAAE,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA;AACvE,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAA,CAAQ,MAAM,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA,EAAG,MAAA,CAAO,CAAC,CAAA,KAAM,MAAA,CAAO,CAAA,CAAE,MAAM,CAAC,CAAA;AAAA,EAC3E,SAAS,KAAA,EAAO;AACd,IAAA,OAAO,EAAE,OAAO,aAAA,EAAe,OAAA,EAAS,iBAAiB,KAAA,GAAQ,KAAA,CAAM,UAAU,4CAAA,EAAoC;AAAA,EACvH;AACA,EAAA,MAAM,MAAA,GAAS,IAAA,CACZ,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,MAAM,GAAA,GAAM,GAAG,CAAA,CAAE,OAAA,IAAW,EAAE,CAAA,CAAA,EAAI,CAAA,CAAE,WAAW,CAAA,CAAA,CAAG,WAAA,EAAY;AAC9D,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,GAAS,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,CAAI,QAAA,CAAS,CAAC,CAAC,CAAA,CAAE,MAAA,GAAS,CAAA;AAC3E,IAAA,OAAO,EAAE,GAAG,KAAA,EAAM;AAAA,EACpB,CAAC,CAAA,CACA,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,CAAA,CAAE,SAAS,CAAA,CAAE,CAAA,CAAE,MAAA,GAAS,CAAA,CAAE,EAAE,MAAM,CAAA;AAC9D,EAAA,MAAM,MAAA,GAAA,CAAU,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,CAAC,CAAA,GAAI,MAAA,EAAQ,KAAA,CAAM,GAAG,EAAE,CAAA;AACtF,EAAA,OAAO;AAAA,IACL,OAAO,MAAA,CAAO,MAAA;AAAA,IACd,WAAW,IAAA,CAAK,MAAA;AAAA,IAChB,UAAU,MAAA,CAAO,GAAA,CAAI,CAAC,EAAE,GAAE,MAAO;AAAA,MAC/B,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,SAAS,CAAA,CAAE,OAAA,IAAW,EAAE,WAAA,CAAY,KAAA,CAAM,GAAG,EAAE,CAAA;AAAA,MAC/C,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,GAAA,EAAK,EAAE,EAAA,EAAI,CAAA,CAAE,IAAI,KAAA,EAAO,CAAA,mBAAA,EAAgB,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI,IAAA,EAAM,CAAA,gBAAA,EAAmB,CAAA,CAAE,EAAE,CAAA,CAAA;AAAG,KACtF,CAAE,CAAA;AAAA,IACF,IAAA,EAAM,MAAA,CAAO,MAAA,GACT,8KAAA,GACA;AAAA,GACN;AACF;AAEA,IAAI,UAAA,GAAa,KAAA;AASV,SAAS,sBAAA,GAA+B;AAC7C,EAAA,IAAI,UAAA,IAAc,OAAO,QAAA,KAAa,WAAA,EAAa;AACnD,EAAA,UAAA,GAAa,IAAA;AACb,EAAA,KAAK,OAAO,gBAAgB,CAAA,CACzB,IAAA,CAAK,CAAC,KAAA,KAAU;AACf,IAAA,mBAAA,GAAsB,KAAA,CAAM,mBAAA;AAC5B,IAAA,KAAA,CAAM,sBAAsB,aAAA,EAAe,CAAC,IAAA,KAAS,kBAAA,CAAmB,IAAI,CAAC,CAAA;AAC7E,IAAA,KAAA,CAAM,sBAAsB,qBAAA,EAAuB,CAAC,IAAA,KAAS,mBAAA,CAAoB,IAAI,CAAC,CAAA;AAAA,EACxF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAGX,IAAA,UAAA,GAAa,KAAA;AAAA,EACf,CAAC,CAAA;AACL;AC/PA,SAAS,GAAG,IAAA,EAAc;AAAE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,MAAA,CAAO,SAAS,IAAA,GAAO,IAAA;AAAK;AAG3F,IAAM,YAAA,GAAgC;AAAA,EACpC,UAAA;AAAA,EAAY,YAAA;AAAA,EAAc,aAAA;AAAA,EAAe,gBAAA;AAAA,EAAkB,gBAAA;AAAA,EAAkB,QAAA;AAAA,EAAU;AACzF,CAAA;AAQO,SAAS,mBAAA,CAAoB,EAAE,QAAA,EAAS,GAA2B,EAAC,EAAuB;AAChG,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,IAAIA,MAAA,CAAM,QAAA,CAA2B,EAAE,CAAA;AACnE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAA,CAAM,SAAS,IAAI,CAAA;AAEjD,EAAAA,MAAA,CAAM,UAAU,MAAM;AACpB,IAAA,IAAI,KAAA,GAAQ,IAAA;AACZ,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,mBAAA,CAAoB,MAAA,EAAW,EAAE,CAAA,CAC9B,IAAA,CAAK,CAAC,CAAA,KAAM;AAAE,MAAA,IAAI,KAAA,EAAO,WAAA,CAAY,QAAA,GAAW,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa,QAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IAAE,CAAC,CAAA,CAC/F,KAAA,CAAM,MAAM;AAAE,MAAA,IAAI,KAAA,EAAO,WAAA,CAAY,EAAE,CAAA;AAAA,IAAE,CAAC,CAAA,CAC1C,OAAA,CAAQ,MAAM;AAAE,MAAA,IAAI,KAAA,aAAkB,KAAK,CAAA;AAAA,IAAE,CAAC,CAAA;AACjD,IAAA,OAAO,MAAM;AAAE,MAAA,KAAA,GAAQ,KAAA;AAAA,IAAM,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,QAAA,GAAW,YAAA,CACd,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,MAAA,EAAQ,CAAA,EAAG,KAAA,EAAO,QAAA,CAAS,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,CAAC,CAAA,EAAE,CAAE,CAAA,CACzE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AAEnC,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,2BAAQ,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EAAiB,QAAA,EAAA,CAAC,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,sBAAM,GAAA,CAAC,QAAA,EAAA,EAAiB,WAAU,aAAA,EAAA,EAAb,CAA2B,CAAE,CAAA,EAAE,CAAA;AAAA,EACzG;AACA,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,IAAA,2BAAQ,GAAA,EAAA,EAAE,SAAA,EAAU,+BAAA,EAAiC,QAAA,EAAA,QAAA,GAAW,mCAAgC,0BAAA,EAA2B,CAAA;AAAA,EAC7H;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA;AAAA,IAAA,CAAC,QAAA,oBAAY,IAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,oCAAA,EAAsC,QAAA,EAAA;AAAA,MAAA,QAAA,CAAS,MAAA;AAAA,MAAO;AAAA,KAAA,EAA8B,CAAA;AAAA,IAC9G,SAAS,GAAA,CAAI,CAAC,sBACb,IAAA,CAAC,KAAA,EAAA,EAAmB,WAAU,uBAAA,EAC5B,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,8BAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,SAAS,WAAA,CAAY,CAAA,CAAE,MAAM,CAAA,EAAI,QAAA,EAAA,YAAA,CAAa,CAAA,CAAE,MAAM,CAAA,EAAE,CAAA;AAAA,4BAC9D,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAA,EAAqC,QAAA,EAAA,CAAA,CAAE,MAAM,MAAA,EAAO;AAAA,OAAA,EACtE,CAAA;AAAA,MACC,CAAA,CAAE,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,qBACZ,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UAEC,SAAS,MAAM,EAAA,CAAG,CAAA,iBAAA,EAAoB,CAAA,CAAE,EAAE,CAAA,CAAE,CAAA;AAAA,UAC5C,SAAA,EAAU,gIAAA;AAAA,UAEV,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2EAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,sCAAA,EAAwC,QAAA,EAAA,CAAA,CAAE,cAAc,MAAA,EAAO,CAAA;AAAA,mCAC9E,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,gBAAA,GAAA;AAAA,gBAAE,CAAA,CAAE;AAAA,eAAA,EAAO;AAAA,aAAA,EACnB,CAAA;AAAA,4BACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kBAAA,EAAoB,QAAA,EAAA,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAE;AAAA;AAAA,SAAA;AAAA,QARtE,CAAA,CAAE;AAAA,OAUV;AAAA,KAAA,EAAA,EAjBO,CAAA,CAAE,MAkBZ,CACD;AAAA,GAAA,EACH,CAAA;AAEJ;;;ACxDA,IAAM,oBAAoBA,MAAAA,CAAM,IAAA,CAAK,MAAM,OAAO,6BAAuB,CAAC,CAAA;AAC1E,IAAM,sBAAsBA,MAAAA,CAAM,IAAA,CAAK,MAAM,OAAO,+BAAyB,CAAC,CAAA;AAI9E,IAAM,mBAAA,GAAyC,MAC7CA,MAAAA,CAAM,aAAA;AAAA,EACJA,MAAAA,CAAM,QAAA;AAAA,EACN,EAAE,QAAA,EAAUA,MAAAA,CAAM,aAAA,CAAc,KAAA,EAAO,EAAE,SAAA,EAAW,mCAAA,EAAoC,EAAG,kBAAa,CAAA,EAAE;AAAA,EAC1GA,MAAAA,CAAM,aAAA,CAAc,mBAAA,EAAqB,IAAI;AAC/C,CAAA;AAeK,SAAS,oBAAoB,OAAA,EAAgD;AAClF,EAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,IAAS,SAAA;AAChC,EAAA,oBAAA,CAAqB,cAAc,CAAA;AAInC,EAAA,sBAAA,EAAuB;AAEvB,EAAA,MAAM,OAAA,GAAgC;AAAA,IACpC,GAAA,EAAK,SAAS,GAAA,IAAO,UAAA;AAAA,IACrB,QAAA,EAAU,SAAS,QAAA,IAAY,eAAA;AAAA,IAC/B,gBAAA,EAAkB,SAAS,gBAAA,IAAoB;AAAA,GACjD;AAEA,EAAA,MAAM,WAAA,GAAiC,MACrCA,MAAAA,CAAM,aAAA;AAAA,IACJA,MAAAA,CAAM,QAAA;AAAA,IACN,EAAE,QAAA,EAAUA,MAAAA,CAAM,aAAA,CAAc,KAAA,EAAO,EAAE,SAAA,EAAW,mCAAA,EAAoC,EAAG,kBAAa,CAAA,EAAE;AAAA,IAC1GA,MAAAA,CAAM,aAAA,CAAc,iBAAA,EAAmB,EAAE,SAAS;AAAA,GACpD;AAEF,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,SAAA;AAAA,IACJ,IAAA,EAAM,KAAA;AAAA,IACN,IAAA,EAAM,UAAA;AAAA,IACN,OAAA,EAAS,OAAA;AAAA,IACT,KAAA,EAAO,SAAS,KAAA,IAAS,WAAA;AAAA,IACzB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,cAAA,EAAgB,IAAA;AAAA,IAChB,cAAc,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOf,gBAAA,EAAkB;AAAA,MAChB,EAAE,EAAA,EAAI,aAAA,EAAe,KAAA,EAAO,sCAAA,EAAkC,OAAO,KAAA;AAAM,KAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,UAAA,EAAY;AAAA,MACV;AAAA,QACE,EAAA,EAAI,iBAAA;AAAA,QACJ,OAAA,EAAS,MAAA;AAAA,QACT,QAAA,EAAU,EAAA;AAAA,QACV,KAAA,EAAO,qBAAA;AAAA,QACP,KAAA,EAAO,kBAAA;AAAA,QACP,IAAA,EAAM,UAAA;AAAA;AAAA;AAAA,QAGN,MAAA,EAAQ,CAAC,WAAA,EAAa,QAAA,EAAU,YAAY,CAAA;AAAA,QAC5C,aAAA,EAAe;AAAA;AACjB,KACF;AAAA;AAAA;AAAA;AAAA,IAKA,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,EAAA,EAAI,SAAA;AAAA,QACJ,KAAA,EAAO,gCAAA;AAAA,QACP,WAAA,EAAa;AAAA;AACf,KACF;AAAA;AAAA;AAAA;AAAA,IAKA,OAAA,EAAS,cAAA;AAAA,IAET,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,UAAA;AAAA,QACN,SAAA,EAAW,WAAA;AAAA,QACX,KAAA,EAAO;AAAA,OACT;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,QAIE,IAAA,EAAM,kBAAA;AAAA,QACN,SAAA,EAAW,mBAAA;AAAA,QACX,KAAA,EAAO,eAAA;AAAA,QACP,KAAA,EAAO;AAAA;AACT,KACF;AAAA,IAEA,UAAA,EAAY,UAAA,CAAW,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MACrC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,EAAE,CAAA,CAAA;AAAA,MACvB,OAAA,EAAS,OAAA;AAAA,MACT,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,WAAA,EACE;AAAA,KACJ,CAAE,CAAA;AAAA,IAEF,OAAA,EAAS;AAAA,GACX;AACF","file":"index.js","sourcesContent":["export const en: Record<string, string> = {\n 'support.title': 'Support',\n 'support.nav.requests': 'My requests',\n 'support.feature.base': 'Support',\n 'support.feature.all': 'Whole-company requests',\n}\n","export const ptBR: Record<string, string> = {\n 'support.title': 'Suporte',\n 'support.nav.requests': 'Minhas solicitações',\n 'support.feature.base': 'Suporte',\n 'support.feature.all': 'Solicitações de toda a empresa',\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const supportLocales: Record<string, Record<string, string>> = {\n en,\n 'pt-BR': ptBR,\n}\n","// AUTO-GENERATED from 000_baseline.sql, 001_helpdesk.sql, 002_operator_attachment_read.sql, 003_operator_attachment_read_fix.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_000_BASELINE = `-- plugin-support — Central de Suporte do Dico: the solicitações a tenant files\n-- (problem / improvement / other), their conversation, history, attachments and\n-- the \"esperando você\" notifications. Port of the V1 support_* family.\n--\n-- Idempotent. Canonical RLS form: tenant_id IN (SELECT public.user_tenant_ids()).\n-- \"Minhas vs Toda a empresa\" is a query-layer filter on created_by, gated by the\n-- support.all feature — not an RLS distinction (the fleet has no SQL permission\n-- helper, and the cross-member triage read is the 2nd wave with the platform bridge).\n\nSET check_function_bodies = false;\n\n-- ── tables ──────────────────────────────────────────────────────────────\n\n-- The request itself. Carries, unseen by the person, the screen it was filed\n-- from (route, active unit, a snapshot of what was visible) — triage reads it first.\nCREATE TABLE IF NOT EXISTS public.plg_support_requests (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n number integer NOT NULL, -- per-tenant sequential #; filled by trigger\n kind text NOT NULL, -- problem | improvement | other\n subject text, -- optional; derived from first line when blank\n description text NOT NULL,\n status text NOT NULL DEFAULT 'received', -- received|in_service|waiting_you|in_development|resolved_check|closed|cancelled\n route text, -- origin screen hash path\n unit_id uuid, -- active unit when filed (informational snapshot, no FK)\n screen_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,\n origin_conversation_id uuid, -- the Dico chat that spawned it, when any\n area text,\n created_by uuid NOT NULL DEFAULT auth.uid(), -- auth user; the owner of \"Minhas\". Default so the client cannot spoof it.\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n resolved_at timestamptz,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb\n);\nALTER TABLE public.plg_support_requests ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant ON public.plg_support_requests(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant_created_by ON public.plg_support_requests(tenant_id, created_by);\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant_status ON public.plg_support_requests(tenant_id, status);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_support_requests_tenant_number ON public.plg_support_requests(tenant_id, number);\n\n-- The conversation inside the request (\"Escreva para a equipe…\").\nCREATE TABLE IF NOT EXISTS public.plg_support_request_messages (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n author_kind text NOT NULL DEFAULT 'user', -- user | team\n author_id uuid DEFAULT auth.uid(),\n body text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_messages ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_messages_tenant ON public.plg_support_request_messages(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_messages_request ON public.plg_support_request_messages(request_id, created_at);\n\n-- The short-sentence history shown in the detail (\"Solicitação aberta\", \"Andamento: Cancelada\").\nCREATE TABLE IF NOT EXISTS public.plg_support_request_events (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n type text NOT NULL, -- opened|status_changed|validated_ok|reopened|cancelled|closed_no_reply|merged\n note text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_events ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_events_tenant ON public.plg_support_request_events(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_events_request ON public.plg_support_request_events(request_id, created_at);\n\n-- Attachment pointers; the file itself lives in the support-attachments bucket.\nCREATE TABLE IF NOT EXISTS public.plg_support_request_attachments (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n storage_path text NOT NULL, -- <tenant_id>/<request_id>/<uuid>/<name>\n name text NOT NULL,\n size_bytes bigint,\n content_type text,\n created_by uuid DEFAULT auth.uid(),\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_attachments ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_attachments_tenant ON public.plg_support_request_attachments(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_attachments_request ON public.plg_support_request_attachments(request_id);\n\n-- The orange \"Esperando você\" counter and the bell notice when the team replies.\nCREATE TABLE IF NOT EXISTS public.plg_support_notifications (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n user_id uuid NOT NULL, -- who should see it\n kind text, -- reply | resolved | waiting_you\n read_at timestamptz, -- NULL = unread (counts in the orange badge)\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_notifications ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_notifications_tenant ON public.plg_support_notifications(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_notifications_user_unread ON public.plg_support_notifications(tenant_id, user_id, read_at);\n\n-- ── per-tenant sequential number ────────────────────────────────────────\n-- Each account counts its requests from #1. The advisory lock serialises the\n-- read-then-write per tenant so two concurrent opens don't collide on the number.\nCREATE OR REPLACE FUNCTION public.plg_support_assign_number()\nRETURNS trigger\nLANGUAGE plpgsql\nAS $fn$\nBEGIN\n IF NEW.number IS NULL OR NEW.number = 0 THEN\n PERFORM pg_advisory_xact_lock(hashtextextended('plg_support_requests:' || NEW.tenant_id::text, 0));\n SELECT COALESCE(MAX(number), 0) + 1 INTO NEW.number\n FROM public.plg_support_requests\n WHERE tenant_id = NEW.tenant_id;\n END IF;\n RETURN NEW;\nEND;\n$fn$;\n\nDROP TRIGGER IF EXISTS trg_plg_support_assign_number ON public.plg_support_requests;\nCREATE TRIGGER trg_plg_support_assign_number\n BEFORE INSERT ON public.plg_support_requests\n FOR EACH ROW EXECUTE FUNCTION public.plg_support_assign_number();\n\n-- ── touch updated_at ────────────────────────────────────────────────────\nCREATE OR REPLACE FUNCTION public.plg_support_touch_updated_at()\nRETURNS trigger\nLANGUAGE plpgsql\nAS $fn$\nBEGIN\n NEW.updated_at := now();\n RETURN NEW;\nEND;\n$fn$;\n\nDROP TRIGGER IF EXISTS trg_plg_support_requests_touch ON public.plg_support_requests;\nCREATE TRIGGER trg_plg_support_requests_touch\n BEFORE UPDATE ON public.plg_support_requests\n FOR EACH ROW EXECUTE FUNCTION public.plg_support_touch_updated_at();\n\n-- ── row security + grants (idempotent) ──────────────────────────────────\nDO $rls$\nDECLARE t text;\nBEGIN\n FOR t IN SELECT unnest(ARRAY[\n 'plg_support_requests',\n 'plg_support_request_messages',\n 'plg_support_request_events',\n 'plg_support_request_attachments',\n 'plg_support_notifications'\n ])\n LOOP\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('GRANT ALL ON public.%I TO authenticated', t);\n EXECUTE format('GRANT ALL ON public.%I TO service_role', t);\n BEGIN\n EXECUTE format('GRANT MAINTAIN ON public.%I TO anon', t);\n EXCEPTION WHEN insufficient_privilege OR undefined_object OR feature_not_supported THEN\n RAISE NOTICE 'plg_support: skipped GRANT MAINTAIN on % (% )', t, SQLERRM;\n END;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_select') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_select', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_insert') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_insert', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_update') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_update', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_delete') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_delete', t);\n END IF;\n END LOOP;\nEND $rls$;\n\n-- ── attachments bucket (private; read by tenant folder) ─────────────────\n-- Path convention: <tenant_id>/<request_id>/<uuid>/<name>. A captura de tela\n-- pode ter nome e valor de cliente borrados mas ainda sensível — bucket privado,\n-- servido por URL assinada de curta duração; quem não é do tenant não alcança.\nINSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)\nVALUES ('support-attachments', 'support-attachments', false, 20971520,\n ARRAY['image/jpeg','image/png','image/webp','image/gif','application/pdf',\n 'text/plain','text/csv','audio/mpeg','audio/webm','video/mp4','video/webm'])\nON CONFLICT (id) DO UPDATE SET\n public = EXCLUDED.public,\n file_size_limit = EXCLUDED.file_size_limit,\n allowed_mime_types = EXCLUDED.allowed_mime_types;\n\nDROP POLICY IF EXISTS plg_support_attachments_read ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_insert ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_update ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_delete ON storage.objects;\n\nCREATE POLICY plg_support_attachments_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_insert ON storage.objects\n FOR INSERT TO authenticated\n WITH CHECK (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_update ON storage.objects\n FOR UPDATE TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text))\n WITH CHECK (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_delete ON storage.objects\n FOR DELETE TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n`\n\nexport const MIGRATION_001_HELPDESK = `-- plugin-support — 2nd wave: the cross-tenant helpdesk the baseline foresaw\n-- (\"the cross-member triage read is the 2nd wave with the platform bridge\").\n--\n-- GoControl is the helpdesk: a small ALLOW-LIST of operators reads and acts on\n-- the support requests of EVERY tenant, through SECURITY DEFINER functions that\n-- carry the tenant boundary themselves. The per-tenant RLS from the baseline is\n-- untouched — a store still sees only its own requests. Only a seeded operator,\n-- and only through these functions, crosses it.\n--\n-- Idempotent. Additive only (new columns, a new table, new functions).\n\nSET check_function_bodies = false;\n\n-- ── new columns on the request ───────────────────────────────────────────\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS priority text NOT NULL DEFAULT 'normal'; -- low|normal|high|urgent\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS linked_lead_id uuid; -- a CRM lead/client the operator tied to it\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS linked_lead_name text; -- denormalised: the operator's tenant, not the requester's\n\n-- ── who may operate the helpdesk ─────────────────────────────────────────\n-- The allow-list is the whole security boundary of the cross-tenant read. A row\n-- here is a GoControl support operator. Nobody writes this from the app; it is\n-- seeded by an admin (see agent-provision / seed script).\nCREATE TABLE IF NOT EXISTS public.plg_support_operators (\n user_id uuid PRIMARY KEY,\n note text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_operators ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON public.plg_support_operators TO authenticated;\nGRANT ALL ON public.plg_support_operators TO service_role;\n-- A user may read ONLY their own row — enough to answer \"am I an operator?\" in\n-- the UI, and it reveals no one else.\nDROP POLICY IF EXISTS plg_support_operators_self ON public.plg_support_operators;\nCREATE POLICY plg_support_operators_self ON public.plg_support_operators\n FOR SELECT TO authenticated USING (user_id = auth.uid());\n\n-- ── gate ──────────────────────────────────────────────────────────────────\n-- Operator = acting IN the helpdesk workspace. The caller's CURRENT tenant\n-- (app.current_tenant_id — the workspace they have open) must be the one that\n-- runs the helpdesk (support \\`console\\` facet active) AND they must be a member\n-- of it. So the console is strictly per-WORKSPACE: it works only while you are\n-- inside GoControl, never from a store, whoever you are. The plg_support_operators\n-- allow-list stays as an explicit extra, but it too only counts while you are in\n-- a helpdesk workspace — it never opens the console from a store.\nCREATE OR REPLACE FUNCTION public.support_is_operator()\nRETURNS boolean\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT EXISTS (\n SELECT 1\n FROM app.tenant_plugins tp\n WHERE tp.plugin_id = 'support' AND tp.facet = 'console' AND tp.status = 'active'\n AND tp.tenant_id = app.current_tenant_id()\n AND (\n EXISTS (\n SELECT 1 FROM app.memberships m\n WHERE m.tenant_id = tp.tenant_id AND m.user_id = auth.uid() AND m.active\n )\n OR EXISTS (SELECT 1 FROM public.plg_support_operators o WHERE o.user_id = auth.uid())\n )\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_is_operator() TO authenticated;\n\n-- A guard the write functions share — raises if the caller is not an operator.\nCREATE OR REPLACE FUNCTION public.support_console_guard()\nRETURNS void\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n IF NOT public.support_is_operator() THEN\n RAISE EXCEPTION 'not a support operator' USING ERRCODE = '42501';\n END IF;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_guard() TO authenticated;\n\n-- Resolve the auth user who opened a ticket to a human name: the person record\n-- in that tenant, else the signed-up full name, else the e-mail. So the console\n-- shows \"Vini\" instead of a uuid.\nCREATE OR REPLACE FUNCTION public._support_user_name(p_user uuid, p_tenant uuid)\nRETURNS text\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT COALESCE(\n (SELECT NULLIF(btrim(p.name), '')\n FROM app.memberships m JOIN public.people p ON p.id = m.person_id\n WHERE m.user_id = p_user AND m.tenant_id = p_tenant LIMIT 1),\n (SELECT NULLIF(btrim(u.raw_user_meta_data->>'full_name'), '') FROM auth.users u WHERE u.id = p_user),\n (SELECT NULLIF(btrim(u.raw_user_meta_data->>'name'), '') FROM auth.users u WHERE u.id = p_user),\n (SELECT u.email FROM auth.users u WHERE u.id = p_user)\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public._support_user_name(uuid, uuid) TO authenticated;\n\n-- ── read: every tenant's requests, for an operator ───────────────────────\nCREATE OR REPLACE FUNCTION public.support_console_requests(\n p_statuses text[] DEFAULT NULL,\n p_search text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE result jsonb;\nBEGIN\n PERFORM public.support_console_guard();\n SELECT COALESCE(jsonb_agg(row_to_json(r)), '[]'::jsonb) INTO result\n FROM (\n SELECT\n q.id, q.number, q.kind, q.subject, q.description, q.status, q.priority,\n q.route, q.created_by, public._support_user_name(q.created_by, q.tenant_id) AS created_by_name,\n q.created_at, q.updated_at,\n q.linked_lead_id, q.linked_lead_name,\n q.tenant_id, t.name AS tenant_name, t.slug AS tenant_slug,\n (SELECT count(*) FROM public.plg_support_request_messages m WHERE m.request_id = q.id) AS message_count\n FROM public.plg_support_requests q\n LEFT JOIN public.tenants t ON t.id = q.tenant_id\n WHERE (p_statuses IS NULL OR q.status = ANY(p_statuses))\n AND (p_search IS NULL OR p_search = ''\n OR q.subject ILIKE '%'||p_search||'%'\n OR q.description ILIKE '%'||p_search||'%'\n OR t.name ILIKE '%'||p_search||'%')\n ORDER BY\n CASE q.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END,\n q.updated_at DESC\n ) r;\n RETURN result;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_requests(text[], text) TO authenticated;\n\n-- ── read: one request in full ────────────────────────────────────────────\nCREATE OR REPLACE FUNCTION public.support_console_detail(p_request_id uuid)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE result jsonb;\nBEGIN\n PERFORM public.support_console_guard();\n SELECT jsonb_build_object(\n 'request', (SELECT row_to_json(x) FROM (\n SELECT q.*, t.name AS tenant_name, t.slug AS tenant_slug,\n public._support_user_name(q.created_by, q.tenant_id) AS created_by_name\n FROM public.plg_support_requests q\n LEFT JOIN public.tenants t ON t.id = q.tenant_id\n WHERE q.id = p_request_id) x),\n 'messages', (SELECT COALESCE(jsonb_agg(row_to_json(m) ORDER BY m.created_at), '[]'::jsonb)\n FROM public.plg_support_request_messages m WHERE m.request_id = p_request_id),\n 'events', (SELECT COALESCE(jsonb_agg(row_to_json(e) ORDER BY e.created_at), '[]'::jsonb)\n FROM public.plg_support_request_events e WHERE e.request_id = p_request_id),\n 'attachments', (SELECT COALESCE(jsonb_agg(row_to_json(a) ORDER BY a.created_at), '[]'::jsonb)\n FROM public.plg_support_request_attachments a WHERE a.request_id = p_request_id)\n ) INTO result;\n RETURN result;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_detail(uuid) TO authenticated;\n\n-- Internal: drop one unread notice to the requester, in their own tenant.\nCREATE OR REPLACE FUNCTION public._support_notify(p_request_id uuid, p_kind text)\nRETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid; v_user uuid;\nBEGIN\n SELECT tenant_id, created_by INTO v_tenant, v_user\n FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_user IS NULL THEN RETURN; END IF;\n INSERT INTO public.plg_support_notifications (tenant_id, request_id, user_id, kind)\n VALUES (v_tenant, p_request_id, v_user, p_kind);\nEND;\n$fn$;\n\n-- ── write: operator changes status (and the client hears about it) ───────\nCREATE OR REPLACE FUNCTION public.support_console_set_status(\n p_request_id uuid, p_status text, p_note text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid;\nBEGIN\n PERFORM public.support_console_guard();\n IF p_status NOT IN ('received','in_service','waiting_you','in_development','resolved_check','closed','cancelled') THEN\n RAISE EXCEPTION 'invalid status %', p_status USING ERRCODE = '22023';\n END IF;\n SELECT tenant_id INTO v_tenant FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'request not found' USING ERRCODE = 'P0002'; END IF;\n UPDATE public.plg_support_requests\n SET status = p_status,\n resolved_at = CASE WHEN p_status IN ('resolved_check','closed') THEN now() ELSE resolved_at END\n WHERE id = p_request_id;\n INSERT INTO public.plg_support_request_events (tenant_id, request_id, type, note)\n VALUES (v_tenant, p_request_id, 'status_changed', p_status);\n PERFORM public._support_notify(p_request_id,\n CASE WHEN p_status IN ('waiting_you','resolved_check') THEN 'waiting_you'\n WHEN p_status IN ('closed') THEN 'resolved' ELSE 'reply' END);\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_set_status(uuid, text, text) TO authenticated;\n\n-- ── write: operator sets priority ────────────────────────────────────────\nCREATE OR REPLACE FUNCTION public.support_console_set_priority(p_request_id uuid, p_priority text)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n PERFORM public.support_console_guard();\n IF p_priority NOT IN ('low','normal','high','urgent') THEN\n RAISE EXCEPTION 'invalid priority %', p_priority USING ERRCODE = '22023';\n END IF;\n UPDATE public.plg_support_requests SET priority = p_priority WHERE id = p_request_id;\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_set_priority(uuid, text) TO authenticated;\n\n-- ── write: operator replies to the requester ─────────────────────────────\nCREATE OR REPLACE FUNCTION public.support_console_reply(p_request_id uuid, p_body text)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid;\nBEGIN\n PERFORM public.support_console_guard();\n IF p_body IS NULL OR btrim(p_body) = '' THEN RAISE EXCEPTION 'empty message' USING ERRCODE = '22023'; END IF;\n SELECT tenant_id INTO v_tenant FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'request not found' USING ERRCODE = 'P0002'; END IF;\n INSERT INTO public.plg_support_request_messages (tenant_id, request_id, author_kind, author_id, body)\n VALUES (v_tenant, p_request_id, 'team', auth.uid(), btrim(p_body));\n PERFORM public._support_notify(p_request_id, 'reply');\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_reply(uuid, text) TO authenticated;\n\n-- ── write: operator links a CRM lead/client ──────────────────────────────\nCREATE OR REPLACE FUNCTION public.support_console_link_lead(\n p_request_id uuid, p_lead_id uuid, p_lead_name text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n PERFORM public.support_console_guard();\n UPDATE public.plg_support_requests\n SET linked_lead_id = p_lead_id, linked_lead_name = p_lead_name\n WHERE id = p_request_id;\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_link_lead(uuid, uuid, text) TO authenticated;\n`\n\nexport const MIGRATION_002_OPERATOR_ATTACHMENT_READ = `-- plugin-support — let a helpdesk OPERATOR open a chamado's attachments.\n-- Idempotent / replay-safe.\n--\n-- The bucket's read policy (000_baseline) scopes every object to its owning\n-- tenant: (storage.foldername(name))[1] IN user_tenant_ids(). That is right for\n-- the store, but the GoControl operator is in ANOTHER tenant, so the signed URL\n-- never comes back and the console shows \"anexo\" instead of the image. This adds\n-- a SECOND, permissive SELECT policy (RLS OR-combines them, so the tenant rule is\n-- untouched) that lets an operator read any support-attachments object — gated by\n-- the same support_is_operator() that guards the whole console. Read only: upload,\n-- update and delete stay tenant-scoped.\nDROP POLICY IF EXISTS plg_support_attachments_operator_read ON storage.objects;\nCREATE POLICY plg_support_attachments_operator_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments' AND public.support_is_operator());\n`\n\nexport const MIGRATION_003_OPERATOR_ATTACHMENT_READ_FIX = `-- plugin-support — make the operator attachment policy actually work from Storage.\n-- Idempotent / replay-safe.\n--\n-- 002 gated the operator read on support_is_operator(), which asks\n-- app.current_tenant_id() — the OPEN workspace. That GUC is set on the normal\n-- data API, but NOT on a Storage sign request, so inside the bucket policy it is\n-- null and the check fails: the operator still got a 400 and saw \"anexo\".\n--\n-- This swaps to a context-independent test that leans only on auth.uid() (always\n-- present from the JWT): the user is a seeded cross-tenant operator, OR an active\n-- member of a tenant that runs the console (the facet). Same intent as\n-- support_is_operator(), minus the current-workspace scoping that Storage cannot\n-- answer. SECURITY DEFINER so the lookups bypass RLS, like the original.\nCREATE OR REPLACE FUNCTION public.support_is_operator_global()\nRETURNS boolean\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT\n EXISTS (SELECT 1 FROM public.plg_support_operators o WHERE o.user_id = auth.uid())\n OR EXISTS (\n SELECT 1\n FROM app.tenant_plugins tp\n JOIN app.memberships m\n ON m.tenant_id = tp.tenant_id AND m.user_id = auth.uid() AND m.active\n WHERE tp.plugin_id = 'support' AND tp.facet = 'console' AND tp.status = 'active'\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_is_operator_global() TO authenticated;\n\nDROP POLICY IF EXISTS plg_support_attachments_operator_read ON storage.objects;\nCREATE POLICY plg_support_attachments_operator_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments' AND public.support_is_operator_global());\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"000_baseline\", sql: MIGRATION_000_BASELINE },\n { id: \"001_helpdesk\", sql: MIGRATION_001_HELPDESK },\n { id: \"002_operator_attachment_read\", sql: MIGRATION_002_OPERATOR_ATTACHMENT_READ },\n { id: \"003_operator_attachment_read_fix\", sql: MIGRATION_003_OPERATOR_ATTACHMENT_READ_FIX },\n]\n","import { useNavHistoryStore, getRecentActivity } from '@fayz-ai/core'\nimport type { PluginAITool } from '@fayz-ai/core'\nimport { createRequest, uploadAttachment, listRequests } from '../data/api'\nimport { useSupportStore } from '../store'\nimport type { PendingAttachment, SupportKind } from '../types'\n\n// Set once admin is loaded (registerSupportAITools) — the images/PDFs the chat\n// turn carried, so a print the user dropped reaches the ticket, not just the LLM.\nlet takeTurnReportFiles: (() => File[]) | null = null\n\n// ---------------------------------------------------------------------------\n// \"Modo relato\" — the Dico registers a support request instead of trying to\n// FIX the problem with whatever tool is lying around.\n//\n// The bug this exists to kill: told \"when I try to reconcile, it won't let me\n// create a supplier\", gpt-4o-mini treated it as a task and called a random\n// payable tool (\"Resolve Payable Context\") — doing nothing the user asked for.\n// A complaint is not a work order. So this tool is a DEAD END on purpose: it\n// writes the request, returns the number, and its description forbids every\n// other tool. The behaviour is driven from BOTH ends — this description and the\n// agent's provisioned instructions (\"report mode\").\n// ---------------------------------------------------------------------------\n\n/** Map whatever the model sends to one of the three ticket kinds. A plain\n * question (\"tenho uma dúvida\") is NOT a ticket — the agent just answers it. */\nfunction normalizeKind(raw: unknown): SupportKind {\n const s = String(raw ?? '').trim().toLowerCase()\n if (['improvement', 'melhoria', 'sugestao', 'sugestão', 'suggestion'].some((k) => s.includes(k))) return 'improvement'\n if (['other', 'outra', 'outras', 'outro', 'solicitacao', 'solicitação', 'request'].some((k) => s.includes(k))) return 'other'\n // Everything else — including \"problem\"/\"problema\"/\"bug\"/\"erro\" — is a problem.\n return 'problem'\n}\n\n/** The last screens the user passed through, newest first. Read from the global\n * nav-history ring buffer the app shell fills on every route change. */\nfunction recentScreens(limit = 10): Array<{ path: string; label: string }> {\n try {\n const stack = useNavHistoryStore.getState().stack\n return stack.slice(-limit).reverse().map((e) => ({ path: e.path, label: e.label }))\n } catch {\n return []\n }\n}\n\n/** The route the user is on right now, as the V1 form records it. */\nfunction currentRoute(): string | null {\n if (typeof location === 'undefined') return null\n return location.hash || location.pathname || null\n}\n\nexport const supportAITools: PluginAITool[] = [\n {\n id: 'support.report-issue',\n name: 'reportIssue',\n description:\n 'REGISTERS a support request (problema, melhoria ou outra solicitação) for the Fayz team. ' +\n 'Use this the moment the user is REPORTING that something is wrong, asking for a change, or raising a request you cannot resolve from the app itself — \"não consigo…\", \"deu erro\", \"não aparece a opção de…\", \"seria bom se…\". ' +\n 'This is the ONLY correct action for a report: do NOT call any other tool to investigate or \"fix\" it (no payable/reconciliation/record tools) — a complaint is not a work order, and guessing at a fix does nothing the user asked for. ' +\n 'Before registering: call findSupportRequests to check the issue is not ALREADY open (if it is, do not duplicate — point the user to that #number). Then use the recent trail you were given to say what the user was doing, confirm the problem in one short question, and offer (do not require) a print. Then call this with a clear description IN THE USER\\'S OWN WORDS. ' +\n 'It returns the request number — tell the user \"registrei a sua solicitação, número #N, a equipe vai acompanhar\" and STOP. The route and the recent screens are captured automatically; you do not pass them.',\n icon: 'LifeBuoy',\n mode: 'persist',\n // No confirmation card: the user already asked for it and the agent confirms\n // in prose. The card would just show the technical params.\n requiresConfirmation: false,\n parameters: {\n type: 'object',\n properties: {\n kind: {\n type: 'string',\n enum: ['problem', 'improvement', 'other'],\n description: 'problem = algo não funcionou; improvement = sugestão de melhoria; other = treinamento, acesso, dados, comercial. Default problem.',\n },\n description: {\n type: 'string',\n description: 'O que aconteceu, na linguagem do usuário: o que ele fazia, o que esperava e o que apareceu.',\n },\n subject: {\n type: 'string',\n description: 'Opcional. Um título curto para a solicitação.',\n },\n },\n required: ['description'],\n },\n category: 'Suporte',\n suggestions: [\n { label: 'Quero reportar um problema', prompt: 'Quero reportar um problema' },\n { label: 'Quero sugerir uma melhoria', prompt: 'Quero sugerir uma melhoria' },\n { label: 'Tenho uma dúvida', prompt: 'Tenho uma dúvida' },\n { label: 'Outra solicitação', prompt: 'Tenho outra solicitação para a equipe' },\n ],\n },\n {\n id: 'support.find-requests',\n name: 'findSupportRequests',\n description:\n 'Lists the user\\'s OPEN support requests (solicitações em aberto). ' +\n 'CALL THIS BEFORE registering a problem or improvement, to check whether the same thing is ALREADY reported. ' +\n 'If an open request clearly covers the same issue, do NOT open a duplicate: tell the user it is already registered (give the #number and its status) and offer to add a note to it instead. ' +\n 'Optional `query` filters by words from the user\\'s complaint.',\n icon: 'Search',\n mode: 'read',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Palavras do que o usuário está relatando, para achar uma solicitação parecida. Opcional.' },\n },\n },\n category: 'Suporte',\n },\n]\n\nexport interface ReportIssueResult {\n ok: boolean\n id?: string\n number?: number\n status?: string\n attachments?: number\n ref?: { id: string; label: string; path: string }\n message?: string\n error?: string\n}\n\n/** Client-plane executor. Writes the request through the plugin's own data\n * layer (createRequest also opens the 'opened' event + mints the per-tenant\n * number via trigger) and hands back the number plus a goto ref the chat\n * renders as a \"Ver solicitação\" button. */\nasync function executeReportIssue(args: Record<string, unknown>): Promise<ReportIssueResult> {\n const description = String(args.description ?? '').trim()\n if (description.length < 5) {\n return {\n ok: false,\n error: 'missing_description',\n message: 'Antes de registrar, pergunte ao usuário o que aconteceu e descreva com as palavras dele (mínimo de uma frase).',\n }\n }\n const kind = normalizeKind(args.kind)\n const subject = typeof args.subject === 'string' && args.subject.trim() ? args.subject.trim() : null\n try {\n const res = await createRequest({\n kind,\n description,\n subject,\n route: currentRoute(),\n // What the user was doing travels with the request, so the team reads it\n // without a second round-trip. `activity` is the rich trail (routes +\n // buttons pressed); `recentScreens` stays for tickets opened before it.\n screenSnapshot: { activity: getRecentActivity(10), recentScreens: recentScreens() },\n })\n // Attach the print the user shared in the chat. Without this the image went\n // only to the model's vision and the ticket had no evidence on it.\n let attached = 0\n const files = takeTurnReportFiles ? takeTurnReportFiles() : []\n for (const file of files) {\n const pending: PendingAttachment = {\n id: crypto.randomUUID(),\n file,\n name: file.name || 'anexo',\n contentType: file.type || 'application/octet-stream',\n sizeBytes: file.size,\n }\n try {\n await uploadAttachment(res.id, pending)\n attached += 1\n } catch {\n // One failed upload never fails the report — the request is already open.\n }\n }\n // If the Central de Suporte is open behind the chat, the agent's write\n // bypasses its store — so \"Minhas solicitações\" would stay empty until a\n // reload. Refresh the list now so the request the user just reported shows.\n try {\n void useSupportStore.getState().loadList()\n } catch {\n // The screen may not be mounted; the list loads on next open regardless.\n }\n return {\n ok: true,\n id: res.id,\n number: res.number,\n status: 'received',\n attachments: attached,\n ref: { id: res.id, label: `Solicitação #${res.number}`, path: `/support/ticket/${res.id}` },\n message:\n `Solicitação #${res.number} registrada${attached ? ` com ${attached} anexo(s)` : ''}. ` +\n 'Diga ao usuário o número e que a equipe vai acompanhar. ' +\n 'NÃO chame mais nenhuma ferramenta — o relato está registrado.',\n }\n } catch (error) {\n return {\n ok: false,\n error: 'write_failed',\n message: error instanceof Error ? error.message : 'Não consegui registrar a solicitação.',\n }\n }\n}\n\n/** Open = still actionable (not closed/cancelled). The dedup check only cares\n * about requests the team could still be working on. */\nfunction isOpen(status: string): boolean {\n return status !== 'closed' && status !== 'cancelled'\n}\n\n/** Read-plane executor: the user's open requests, ranked by how well they match\n * the complaint, so the agent can see \"this is already #N\" before duplicating. */\nasync function executeFindRequests(args: Record<string, unknown>): Promise<unknown> {\n const query = String(args.query ?? '').trim().toLowerCase()\n const terms = query.split(/[^\\p{L}\\p{N}]+/u).filter((w) => w.length > 2)\n let open\n try {\n open = (await listRequests('mine', 'all')).filter((r) => isOpen(r.status))\n } catch (error) {\n return { error: 'read_failed', message: error instanceof Error ? error.message : 'Não consegui ler as solicitações.' }\n }\n const scored = open\n .map((r) => {\n const hay = `${r.subject ?? ''} ${r.description}`.toLowerCase()\n const score = terms.length ? terms.filter((t) => hay.includes(t)).length : 0\n return { r, score }\n })\n .sort((a, b) => b.score - a.score || b.r.number - a.r.number)\n const picked = (terms.length ? scored.filter((s) => s.score > 0) : scored).slice(0, 10)\n return {\n total: picked.length,\n openTotal: open.length,\n requests: picked.map(({ r }) => ({\n number: r.number,\n kind: r.kind,\n subject: r.subject || r.description.slice(0, 80),\n status: r.status,\n createdAt: r.createdAt,\n ref: { id: r.id, label: `Solicitação #${r.number}`, path: `/support/ticket/${r.id}` },\n })),\n hint: picked.length\n ? 'If one of these is clearly the SAME issue the user is reporting now, do not open a duplicate — say it is already registered (#number + status) and offer to add a note.'\n : 'Nothing similar is open — go ahead and register the new request.',\n }\n}\n\nlet registered = false\n\n/**\n * Register the client-plane executor for `reportIssue`.\n *\n * Done through a DYNAMIC import of @fayz-ai/admin, guarded to the browser, so\n * building the manifest (and unit-testing it) never pulls the admin React chain\n * — the same reason index.ts lazy-loads the views. Idempotent.\n */\nexport function registerSupportAITools(): void {\n if (registered || typeof document === 'undefined') return\n registered = true\n void import('@fayz-ai/admin')\n .then((admin) => {\n takeTurnReportFiles = admin.takeTurnReportFiles\n admin.registerAIToolHandler('reportIssue', (args) => executeReportIssue(args))\n admin.registerAIToolHandler('findSupportRequests', (args) => executeFindRequests(args))\n })\n .catch(() => {\n // An app with no assistant wired never calls the tool — a failed register\n // must not break the plugin's screens.\n registered = false\n })\n}\n","import React from 'react'\nimport { Badge, Skeleton } from '@fayz-ai/ui'\nimport { consoleListRequests, type ConsoleRequest } from '../data/console'\nimport { STATUS_LABEL, STATUS_TONE } from '../lib/vocabulary'\nimport type { SupportStatus } from '../types'\n\n/** Hash navigation, without reaching into the shell's private routing. */\nfunction go(path: string) { if (typeof window !== 'undefined') window.location.hash = path }\n\n// Open first, then the closed ones.\nconst STATUS_ORDER: SupportStatus[] = [\n 'received', 'in_service', 'waiting_you', 'in_development', 'resolved_check', 'closed', 'cancelled',\n]\n\n/**\n * The helpdesk's chamados grouped by status. With `tenantId` it is ONE store's\n * tickets — drawn inside that store's CRM card (the store is resolved from the\n * CRM company's metadata.tenant_id_v2). Without it, every store's. Reads the same\n * cross-tenant console RPC (operator-gated); each row deep-links to the ticket.\n */\nexport function HelpdeskStatusPanel({ tenantId }: { tenantId?: string } = {}): React.ReactElement {\n const [requests, setRequests] = React.useState<ConsoleRequest[]>([])\n const [loading, setLoading] = React.useState(true)\n\n React.useEffect(() => {\n let alive = true\n setLoading(true)\n consoleListRequests(undefined, '')\n .then((r) => { if (alive) setRequests(tenantId ? r.filter((x) => x.tenantId === tenantId) : r) })\n .catch(() => { if (alive) setRequests([]) })\n .finally(() => { if (alive) setLoading(false) })\n return () => { alive = false }\n }, [tenantId])\n\n const byStatus = STATUS_ORDER\n .map((s) => ({ status: s, items: requests.filter((r) => r.status === s) }))\n .filter((g) => g.items.length > 0)\n\n if (loading) {\n return <div className=\"space-y-2 p-1\">{[0, 1].map((i) => <Skeleton key={i} className=\"h-14 w-full\" />)}</div>\n }\n if (requests.length === 0) {\n return <p className=\"text-xs text-muted-foreground\">{tenantId ? 'Esta loja não tem chamados.' : 'Nenhum chamado por aqui.'}</p>\n }\n\n return (\n <div className=\"flex flex-col gap-3\">\n {!tenantId && <p className=\"px-1 text-xs text-muted-foreground\">{requests.length} chamado(s) de todas as lojas.</p>}\n {byStatus.map((g) => (\n <div key={g.status} className=\"flex flex-col gap-1.5\">\n <div className=\"flex items-center gap-2 px-1\">\n <Badge variant={STATUS_TONE[g.status]}>{STATUS_LABEL[g.status]}</Badge>\n <span className=\"text-[11px] text-muted-foreground\">{g.items.length}</span>\n </div>\n {g.items.map((r) => (\n <button\n key={r.id}\n onClick={() => go(`/support/console/${r.id}`)}\n className=\"flex w-full flex-col gap-0.5 rounded-lg border bg-card p-2 text-left transition-colors hover:border-primary/40 hover:bg-accent\"\n >\n <div className=\"flex items-center justify-between gap-2 text-[11px] text-muted-foreground\">\n <span className=\"truncate font-medium text-foreground\">{r.tenantName ?? 'Loja'}</span>\n <span>#{r.number}</span>\n </div>\n <div className=\"truncate text-xs\">{r.subject || r.description.slice(0, 60)}</div>\n </button>\n ))}\n </div>\n ))}\n </div>\n )\n}\n\nexport default HelpdeskStatusPanel\n","import React from 'react'\nimport type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { registerTranslations } from '@fayz-ai/core'\nimport { supportLocales } from './locales'\nimport { MIGRATIONS } from './migrations'\nimport {\n defaultFaq, defaultWhatsNew, defaultStatusComponents,\n type FaqItem, type WhatsNewItem, type StatusComponent,\n} from './lib/content'\nimport { supportAITools, registerSupportAITools } from './lib/report-tool'\nimport type { SupportCenterContent } from './views/SupportCenter'\n\n// Lazy so building the manifest (and unit-testing it) does not eagerly pull the\n// React views and their @fayz-ai/admin chain. The wrapper owns its own Suspense,\n// so the route renders whether or not the shell provides one.\nconst LazySupportCenter = React.lazy(() => import('./views/SupportCenter'))\nconst LazyHelpdeskConsole = React.lazy(() => import('./views/HelpdeskConsole'))\n\n/** The helpdesk console, wrapped in its own Suspense (same reason the center is\n * lazy: keep the manifest free of the React/admin chain). */\nconst HelpdeskConsolePage: React.FC<unknown> = () =>\n React.createElement(\n React.Suspense,\n { fallback: React.createElement('div', { className: 'p-6 text-sm text-muted-foreground' }, 'Carregando…') },\n React.createElement(LazyHelpdeskConsole, null),\n )\n\nexport interface SupportPluginOptions {\n /** Label for the plugin and the user-menu entry (default \"Suporte\"). */\n label?: string\n scope?: PluginScope\n verticalId?: VerticalId\n /** Knowledge-base content — overrides the sample FAQ that ships by default. */\n faq?: FaqItem[]\n /** Release notes shown in the \"Novidades\" card. */\n whatsNew?: WhatsNewItem[]\n /** Status components listed under the Status card. */\n statusComponents?: StatusComponent[]\n}\n\nexport function createSupportPlugin(options?: SupportPluginOptions): PluginManifest {\n const label = options?.label ?? 'Suporte'\n registerTranslations(supportLocales)\n // Make the Dico's \"modo relato\" tool runnable (client-plane executor). Guarded\n // to the browser inside, so this is a no-op when the manifest is built on the\n // server or in a unit test.\n registerSupportAITools()\n\n const content: SupportCenterContent = {\n faq: options?.faq ?? defaultFaq,\n whatsNew: options?.whatsNew ?? defaultWhatsNew,\n statusComponents: options?.statusComponents ?? defaultStatusComponents,\n }\n\n const SupportPage: React.FC<unknown> = () =>\n React.createElement(\n React.Suspense,\n { fallback: React.createElement('div', { className: 'p-6 text-sm text-muted-foreground' }, 'Carregando…') },\n React.createElement(LazySupportCenter, { content }),\n )\n\n return {\n id: 'support',\n name: label,\n icon: 'LifeBuoy',\n version: '0.1.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n defaultEnabled: true,\n dependencies: [],\n\n // Support is for everyone, like the V1 support panel (\"nasce liberada em\n // todos os perfis\") — so the route is open to any authenticated user rather\n // than gated by a deny-by-default feature. Only `support.all` is a declared\n // permission: it unlocks the \"Toda a empresa\" view, and ships denied (the\n // admin/owner holds it).\n declaredFeatures: [\n { id: 'support.all', label: 'Solicitações de toda a empresa', group: label },\n ],\n\n // The requester reaches Suporte from the user menu. The OPERATOR console line\n // is gated by the `console` FACET — it shows ONLY in the workspace that runs\n // the helpdesk (GoControl), where the facet is on. In a store (Pertinho,\n // Artorius…) the facet is off, so the line is absent no matter who is logged\n // in. It is a per-TENANT line: you see it when you are IN the GoControl\n // workspace, not because of who you are.\n navigation: [\n {\n id: 'support-console',\n section: 'main',\n position: 45,\n label: 'Central de Chamados',\n route: '/support/console',\n icon: 'LifeBuoy',\n // Sit right below \"Clientes\": land above the line the rail draws next\n // after it (AtendeAI, then the CRM/financial block).\n before: ['/atendeai', '/sales', '/financial'],\n requiresFacet: 'console',\n },\n ],\n\n // Off for every tenant by default; enabled only for the helpdesk workspace\n // with scripts/support-enable-console.mjs. A store that turns on Suporte does\n // NOT get it.\n facets: [\n {\n id: 'console',\n label: 'Central de Chamados (operador)',\n description: 'O console cross-tenant do suporte — só no workspace que opera o helpdesk.',\n },\n ],\n\n // \"Modo relato\": the assistant registers a request instead of improvising a\n // fix. The executor is wired above (registerSupportAITools); the opener\n // suggestions ride on the tool (the V1 doors).\n aiTools: supportAITools,\n\n routes: [\n {\n path: '/support',\n component: SupportPage as React.ComponentType<unknown>,\n guard: 'authenticated',\n },\n {\n // The helpdesk console. Open to any authenticated user as a route; the\n // view calls support_is_operator() and shows \"acesso restrito\" to a\n // non-operator, and every data call is gated server-side anyway.\n path: '/support/console',\n component: HelpdeskConsolePage as React.ComponentType<unknown>,\n guard: 'authenticated',\n title: 'Central de Chamados',\n },\n ],\n\n migrations: MIGRATIONS.map((entry) => ({\n id: `support-${entry.id}`,\n version: '0.1.0',\n sql: entry.sql,\n description:\n 'Central de Suporte do Dico: the plg_support_* tables (requests, messages, events, attachments, notifications), their RLS, the per-tenant number trigger and the private support-attachments bucket.',\n })),\n\n locales: supportLocales,\n }\n}\n\nexport { useSupportStore } from './store'\n// A store's chamados by status, for embedding in that store's CRM card (the CRM\n// resolves the store tenant from company.metadata.tenant_id_v2 and passes it).\nexport { HelpdeskStatusPanel } from './components/HelpdeskStatusPanel'\nexport * from './types'\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface FaqItem {
|
|
2
|
+
id: string;
|
|
3
|
+
question: string;
|
|
4
|
+
answer: string;
|
|
5
|
+
/** YouTube/Vimeo watch or embed URL, or a direct mp4 — optional explainer video. */
|
|
6
|
+
videoUrl?: string;
|
|
7
|
+
category?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WhatsNewItem {
|
|
10
|
+
id: string;
|
|
11
|
+
date: string;
|
|
12
|
+
title: string;
|
|
13
|
+
note?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const defaultFaq: FaqItem[];
|
|
16
|
+
export declare const defaultWhatsNew: WhatsNewItem[];
|
|
17
|
+
export interface StatusComponent {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
export declare const defaultStatusComponents: StatusComponent[];
|
|
22
|
+
/** Turn a watch/share URL into an embeddable one; pass through direct files. */
|
|
23
|
+
export declare function toEmbedUrl(url: string): string;
|
|
24
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/lib/content.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,UAAU,EAAE,OAAO,EA6B/B,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,YAAY,EAOzC,CAAA;AAID,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,uBAAuB,EAAE,eAAe,EAGpD,CAAA;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SupportKind, SupportStatus, SupportMessage, SupportEvent, SupportAttachment } from '../types';
|
|
2
|
+
/** The fields the prompt needs — satisfied by both the requester's
|
|
3
|
+
* SupportRequestDetail and the operator's ConsoleDetail. */
|
|
4
|
+
export interface FixPromptInput {
|
|
5
|
+
number: number;
|
|
6
|
+
kind: SupportKind;
|
|
7
|
+
status: SupportStatus;
|
|
8
|
+
subject: string | null;
|
|
9
|
+
description: string;
|
|
10
|
+
route: string | null;
|
|
11
|
+
screenSnapshot: Record<string, unknown>;
|
|
12
|
+
messages: SupportMessage[];
|
|
13
|
+
events: SupportEvent[];
|
|
14
|
+
attachments: SupportAttachment[];
|
|
15
|
+
/** The store (tenant) the ticket is from, and the user who reported it — so a
|
|
16
|
+
* coding agent can resolve the exact account and reproduce against it. */
|
|
17
|
+
tenantId?: string;
|
|
18
|
+
createdBy?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The prompt an operator copies into Claude to go fix a reported issue.
|
|
22
|
+
*
|
|
23
|
+
* It carries everything Claude needs to find the cause without a second
|
|
24
|
+
* round-trip: which app and screen, the user's own words, the trail of routes
|
|
25
|
+
* and buttons that led to it, and the conversation so far. English scaffolding
|
|
26
|
+
* (it is read by a coding agent) around the user's pt-BR text (left verbatim —
|
|
27
|
+
* the exact words are the bug report).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildFixPrompt(detail: FixPromptInput, appName?: string): string;
|
|
30
|
+
//# sourceMappingURL=fix-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix-prompt.d.ts","sourceRoot":"","sources":["../../src/lib/fix-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAG3G;6DAC6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,WAAW,EAAE,iBAAiB,EAAE,CAAA;IAChC;+EAC2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAuD/E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PluginAITool } from '@fayz-ai/core';
|
|
2
|
+
export declare const supportAITools: PluginAITool[];
|
|
3
|
+
export interface ReportIssueResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
id?: string;
|
|
6
|
+
number?: number;
|
|
7
|
+
status?: string;
|
|
8
|
+
attachments?: number;
|
|
9
|
+
ref?: {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
path: string;
|
|
13
|
+
};
|
|
14
|
+
message?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register the client-plane executor for `reportIssue`.
|
|
19
|
+
*
|
|
20
|
+
* Done through a DYNAMIC import of @fayz-ai/admin, guarded to the browser, so
|
|
21
|
+
* building the manifest (and unit-testing it) never pulls the admin React chain
|
|
22
|
+
* — the same reason index.ts lazy-loads the views. Idempotent.
|
|
23
|
+
*/
|
|
24
|
+
export declare function registerSupportAITools(): void;
|
|
25
|
+
//# sourceMappingURL=report-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-tool.d.ts","sourceRoot":"","sources":["../../src/lib/report-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAiDjD,eAAO,MAAM,cAAc,EAAE,YAAY,EA4DxC,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAA;IACX,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAwHD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAc7C"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SupportKind, SupportStatus, SupportFilter, SupportEventType, SupportPriority } from '../types';
|
|
2
|
+
export declare const KIND_LABEL: Record<SupportKind, string>;
|
|
3
|
+
export declare const STATUS_LABEL: Record<SupportStatus, string>;
|
|
4
|
+
export type BadgeTone = 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline';
|
|
5
|
+
export declare const PRIORITY_LABEL: Record<SupportPriority, string>;
|
|
6
|
+
export declare const PRIORITY_TONE: Record<SupportPriority, BadgeTone>;
|
|
7
|
+
/** Triage order: urgent first. */
|
|
8
|
+
export declare const PRIORITY_ORDER: SupportPriority[];
|
|
9
|
+
export declare const STATUS_TONE: Record<SupportStatus, BadgeTone>;
|
|
10
|
+
export declare const FILTER_STATUSES: Record<Exclude<SupportFilter, 'all'>, SupportStatus[]>;
|
|
11
|
+
export declare const FILTER_LABEL: Record<SupportFilter, string>;
|
|
12
|
+
/** The statuses that still let the person write / attach. */
|
|
13
|
+
export declare function canMessage(status: SupportStatus): boolean;
|
|
14
|
+
/** Cancel is only offered before the team picks it up. */
|
|
15
|
+
export declare function canCancel(status: SupportStatus): boolean;
|
|
16
|
+
/** "Ficou bom / Ainda não resolveu" replaces the message box here. */
|
|
17
|
+
export declare function canValidate(status: SupportStatus): boolean;
|
|
18
|
+
export declare const DOORS: Array<{
|
|
19
|
+
kind: SupportKind;
|
|
20
|
+
title: string;
|
|
21
|
+
hint: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const FORM_COPY: Record<SupportKind, {
|
|
25
|
+
title: string;
|
|
26
|
+
intro: string;
|
|
27
|
+
subjectPlaceholder: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const EVENT_LABEL: Record<SupportEventType, string>;
|
|
30
|
+
/** The history line as shown: "Andamento: Em atendimento" etc. */
|
|
31
|
+
export declare function eventLine(type: SupportEventType, note: string | null): string;
|
|
32
|
+
/** Subject fallback: first line/sentence of the description, capped at 200. */
|
|
33
|
+
export declare function deriveSubject(description: string): string;
|
|
34
|
+
//# sourceMappingURL=vocabulary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/lib/vocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAK5G,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAIlD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAQtD,CAAA;AAGD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAA;AAGnG,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAK1D,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,SAAS,CAK5D,CAAA;AAED,kCAAkC;AAClC,eAAO,MAAM,cAAc,EAAE,eAAe,EAAwC,CAAA;AAEpF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,aAAa,EAAE,SAAS,CAQxD,CAAA;AAGD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,EAAE,CAIlF,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAKtD,CAAA;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAEzD;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAExD;AAED,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAE1D;AAGD,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC;IACxB,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAIA,CAAA;AAGD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAgBvG,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAQxD,CAAA;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAO7E;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAIzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKrC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/locales/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGjE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKvC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MIGRATION_000_BASELINE = "-- plugin-support \u2014 Central de Suporte do Dico: the solicita\u00E7\u00F5es a tenant files\n-- (problem / improvement / other), their conversation, history, attachments and\n-- the \"esperando voc\u00EA\" notifications. Port of the V1 support_* family.\n--\n-- Idempotent. Canonical RLS form: tenant_id IN (SELECT public.user_tenant_ids()).\n-- \"Minhas vs Toda a empresa\" is a query-layer filter on created_by, gated by the\n-- support.all feature \u2014 not an RLS distinction (the fleet has no SQL permission\n-- helper, and the cross-member triage read is the 2nd wave with the platform bridge).\n\nSET check_function_bodies = false;\n\n-- \u2500\u2500 tables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n-- The request itself. Carries, unseen by the person, the screen it was filed\n-- from (route, active unit, a snapshot of what was visible) \u2014 triage reads it first.\nCREATE TABLE IF NOT EXISTS public.plg_support_requests (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n number integer NOT NULL, -- per-tenant sequential #; filled by trigger\n kind text NOT NULL, -- problem | improvement | other\n subject text, -- optional; derived from first line when blank\n description text NOT NULL,\n status text NOT NULL DEFAULT 'received', -- received|in_service|waiting_you|in_development|resolved_check|closed|cancelled\n route text, -- origin screen hash path\n unit_id uuid, -- active unit when filed (informational snapshot, no FK)\n screen_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,\n origin_conversation_id uuid, -- the Dico chat that spawned it, when any\n area text,\n created_by uuid NOT NULL DEFAULT auth.uid(), -- auth user; the owner of \"Minhas\". Default so the client cannot spoof it.\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n resolved_at timestamptz,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb\n);\nALTER TABLE public.plg_support_requests ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant ON public.plg_support_requests(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant_created_by ON public.plg_support_requests(tenant_id, created_by);\nCREATE INDEX IF NOT EXISTS idx_plg_support_requests_tenant_status ON public.plg_support_requests(tenant_id, status);\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_support_requests_tenant_number ON public.plg_support_requests(tenant_id, number);\n\n-- The conversation inside the request (\"Escreva para a equipe\u2026\").\nCREATE TABLE IF NOT EXISTS public.plg_support_request_messages (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n author_kind text NOT NULL DEFAULT 'user', -- user | team\n author_id uuid DEFAULT auth.uid(),\n body text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_messages ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_messages_tenant ON public.plg_support_request_messages(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_messages_request ON public.plg_support_request_messages(request_id, created_at);\n\n-- The short-sentence history shown in the detail (\"Solicita\u00E7\u00E3o aberta\", \"Andamento: Cancelada\").\nCREATE TABLE IF NOT EXISTS public.plg_support_request_events (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n type text NOT NULL, -- opened|status_changed|validated_ok|reopened|cancelled|closed_no_reply|merged\n note text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_events ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_events_tenant ON public.plg_support_request_events(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_events_request ON public.plg_support_request_events(request_id, created_at);\n\n-- Attachment pointers; the file itself lives in the support-attachments bucket.\nCREATE TABLE IF NOT EXISTS public.plg_support_request_attachments (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n storage_path text NOT NULL, -- <tenant_id>/<request_id>/<uuid>/<name>\n name text NOT NULL,\n size_bytes bigint,\n content_type text,\n created_by uuid DEFAULT auth.uid(),\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_request_attachments ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_attachments_tenant ON public.plg_support_request_attachments(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_request_attachments_request ON public.plg_support_request_attachments(request_id);\n\n-- The orange \"Esperando voc\u00EA\" counter and the bell notice when the team replies.\nCREATE TABLE IF NOT EXISTS public.plg_support_notifications (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n request_id uuid NOT NULL REFERENCES public.plg_support_requests(id) ON DELETE CASCADE,\n user_id uuid NOT NULL, -- who should see it\n kind text, -- reply | resolved | waiting_you\n read_at timestamptz, -- NULL = unread (counts in the orange badge)\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_notifications ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_support_notifications_tenant ON public.plg_support_notifications(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_support_notifications_user_unread ON public.plg_support_notifications(tenant_id, user_id, read_at);\n\n-- \u2500\u2500 per-tenant sequential number \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Each account counts its requests from #1. The advisory lock serialises the\n-- read-then-write per tenant so two concurrent opens don't collide on the number.\nCREATE OR REPLACE FUNCTION public.plg_support_assign_number()\nRETURNS trigger\nLANGUAGE plpgsql\nAS $fn$\nBEGIN\n IF NEW.number IS NULL OR NEW.number = 0 THEN\n PERFORM pg_advisory_xact_lock(hashtextextended('plg_support_requests:' || NEW.tenant_id::text, 0));\n SELECT COALESCE(MAX(number), 0) + 1 INTO NEW.number\n FROM public.plg_support_requests\n WHERE tenant_id = NEW.tenant_id;\n END IF;\n RETURN NEW;\nEND;\n$fn$;\n\nDROP TRIGGER IF EXISTS trg_plg_support_assign_number ON public.plg_support_requests;\nCREATE TRIGGER trg_plg_support_assign_number\n BEFORE INSERT ON public.plg_support_requests\n FOR EACH ROW EXECUTE FUNCTION public.plg_support_assign_number();\n\n-- \u2500\u2500 touch updated_at \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.plg_support_touch_updated_at()\nRETURNS trigger\nLANGUAGE plpgsql\nAS $fn$\nBEGIN\n NEW.updated_at := now();\n RETURN NEW;\nEND;\n$fn$;\n\nDROP TRIGGER IF EXISTS trg_plg_support_requests_touch ON public.plg_support_requests;\nCREATE TRIGGER trg_plg_support_requests_touch\n BEFORE UPDATE ON public.plg_support_requests\n FOR EACH ROW EXECUTE FUNCTION public.plg_support_touch_updated_at();\n\n-- \u2500\u2500 row security + grants (idempotent) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDO $rls$\nDECLARE t text;\nBEGIN\n FOR t IN SELECT unnest(ARRAY[\n 'plg_support_requests',\n 'plg_support_request_messages',\n 'plg_support_request_events',\n 'plg_support_request_attachments',\n 'plg_support_notifications'\n ])\n LOOP\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('GRANT ALL ON public.%I TO authenticated', t);\n EXECUTE format('GRANT ALL ON public.%I TO service_role', t);\n BEGIN\n EXECUTE format('GRANT MAINTAIN ON public.%I TO anon', t);\n EXCEPTION WHEN insufficient_privilege OR undefined_object OR feature_not_supported THEN\n RAISE NOTICE 'plg_support: skipped GRANT MAINTAIN on % (% )', t, SQLERRM;\n END;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_select') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_select', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_insert') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_insert', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_update') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_update', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_delete') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_delete', t);\n END IF;\n END LOOP;\nEND $rls$;\n\n-- \u2500\u2500 attachments bucket (private; read by tenant folder) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Path convention: <tenant_id>/<request_id>/<uuid>/<name>. A captura de tela\n-- pode ter nome e valor de cliente borrados mas ainda sens\u00EDvel \u2014 bucket privado,\n-- servido por URL assinada de curta dura\u00E7\u00E3o; quem n\u00E3o \u00E9 do tenant n\u00E3o alcan\u00E7a.\nINSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)\nVALUES ('support-attachments', 'support-attachments', false, 20971520,\n ARRAY['image/jpeg','image/png','image/webp','image/gif','application/pdf',\n 'text/plain','text/csv','audio/mpeg','audio/webm','video/mp4','video/webm'])\nON CONFLICT (id) DO UPDATE SET\n public = EXCLUDED.public,\n file_size_limit = EXCLUDED.file_size_limit,\n allowed_mime_types = EXCLUDED.allowed_mime_types;\n\nDROP POLICY IF EXISTS plg_support_attachments_read ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_insert ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_update ON storage.objects;\nDROP POLICY IF EXISTS plg_support_attachments_delete ON storage.objects;\n\nCREATE POLICY plg_support_attachments_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_insert ON storage.objects\n FOR INSERT TO authenticated\n WITH CHECK (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_update ON storage.objects\n FOR UPDATE TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text))\n WITH CHECK (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n\nCREATE POLICY plg_support_attachments_delete ON storage.objects\n FOR DELETE TO authenticated\n USING (bucket_id = 'support-attachments'\n AND (storage.foldername(name))[1] IN (SELECT public.user_tenant_ids()::text));\n";
|
|
2
|
+
export declare const MIGRATION_001_HELPDESK = "-- plugin-support \u2014 2nd wave: the cross-tenant helpdesk the baseline foresaw\n-- (\"the cross-member triage read is the 2nd wave with the platform bridge\").\n--\n-- GoControl is the helpdesk: a small ALLOW-LIST of operators reads and acts on\n-- the support requests of EVERY tenant, through SECURITY DEFINER functions that\n-- carry the tenant boundary themselves. The per-tenant RLS from the baseline is\n-- untouched \u2014 a store still sees only its own requests. Only a seeded operator,\n-- and only through these functions, crosses it.\n--\n-- Idempotent. Additive only (new columns, a new table, new functions).\n\nSET check_function_bodies = false;\n\n-- \u2500\u2500 new columns on the request \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS priority text NOT NULL DEFAULT 'normal'; -- low|normal|high|urgent\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS linked_lead_id uuid; -- a CRM lead/client the operator tied to it\nALTER TABLE public.plg_support_requests\n ADD COLUMN IF NOT EXISTS linked_lead_name text; -- denormalised: the operator's tenant, not the requester's\n\n-- \u2500\u2500 who may operate the helpdesk \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- The allow-list is the whole security boundary of the cross-tenant read. A row\n-- here is a GoControl support operator. Nobody writes this from the app; it is\n-- seeded by an admin (see agent-provision / seed script).\nCREATE TABLE IF NOT EXISTS public.plg_support_operators (\n user_id uuid PRIMARY KEY,\n note text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_support_operators ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON public.plg_support_operators TO authenticated;\nGRANT ALL ON public.plg_support_operators TO service_role;\n-- A user may read ONLY their own row \u2014 enough to answer \"am I an operator?\" in\n-- the UI, and it reveals no one else.\nDROP POLICY IF EXISTS plg_support_operators_self ON public.plg_support_operators;\nCREATE POLICY plg_support_operators_self ON public.plg_support_operators\n FOR SELECT TO authenticated USING (user_id = auth.uid());\n\n-- \u2500\u2500 gate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Operator = acting IN the helpdesk workspace. The caller's CURRENT tenant\n-- (app.current_tenant_id \u2014 the workspace they have open) must be the one that\n-- runs the helpdesk (support `console` facet active) AND they must be a member\n-- of it. So the console is strictly per-WORKSPACE: it works only while you are\n-- inside GoControl, never from a store, whoever you are. The plg_support_operators\n-- allow-list stays as an explicit extra, but it too only counts while you are in\n-- a helpdesk workspace \u2014 it never opens the console from a store.\nCREATE OR REPLACE FUNCTION public.support_is_operator()\nRETURNS boolean\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT EXISTS (\n SELECT 1\n FROM app.tenant_plugins tp\n WHERE tp.plugin_id = 'support' AND tp.facet = 'console' AND tp.status = 'active'\n AND tp.tenant_id = app.current_tenant_id()\n AND (\n EXISTS (\n SELECT 1 FROM app.memberships m\n WHERE m.tenant_id = tp.tenant_id AND m.user_id = auth.uid() AND m.active\n )\n OR EXISTS (SELECT 1 FROM public.plg_support_operators o WHERE o.user_id = auth.uid())\n )\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_is_operator() TO authenticated;\n\n-- A guard the write functions share \u2014 raises if the caller is not an operator.\nCREATE OR REPLACE FUNCTION public.support_console_guard()\nRETURNS void\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n IF NOT public.support_is_operator() THEN\n RAISE EXCEPTION 'not a support operator' USING ERRCODE = '42501';\n END IF;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_guard() TO authenticated;\n\n-- Resolve the auth user who opened a ticket to a human name: the person record\n-- in that tenant, else the signed-up full name, else the e-mail. So the console\n-- shows \"Vini\" instead of a uuid.\nCREATE OR REPLACE FUNCTION public._support_user_name(p_user uuid, p_tenant uuid)\nRETURNS text\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT COALESCE(\n (SELECT NULLIF(btrim(p.name), '')\n FROM app.memberships m JOIN public.people p ON p.id = m.person_id\n WHERE m.user_id = p_user AND m.tenant_id = p_tenant LIMIT 1),\n (SELECT NULLIF(btrim(u.raw_user_meta_data->>'full_name'), '') FROM auth.users u WHERE u.id = p_user),\n (SELECT NULLIF(btrim(u.raw_user_meta_data->>'name'), '') FROM auth.users u WHERE u.id = p_user),\n (SELECT u.email FROM auth.users u WHERE u.id = p_user)\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public._support_user_name(uuid, uuid) TO authenticated;\n\n-- \u2500\u2500 read: every tenant's requests, for an operator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_requests(\n p_statuses text[] DEFAULT NULL,\n p_search text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE result jsonb;\nBEGIN\n PERFORM public.support_console_guard();\n SELECT COALESCE(jsonb_agg(row_to_json(r)), '[]'::jsonb) INTO result\n FROM (\n SELECT\n q.id, q.number, q.kind, q.subject, q.description, q.status, q.priority,\n q.route, q.created_by, public._support_user_name(q.created_by, q.tenant_id) AS created_by_name,\n q.created_at, q.updated_at,\n q.linked_lead_id, q.linked_lead_name,\n q.tenant_id, t.name AS tenant_name, t.slug AS tenant_slug,\n (SELECT count(*) FROM public.plg_support_request_messages m WHERE m.request_id = q.id) AS message_count\n FROM public.plg_support_requests q\n LEFT JOIN public.tenants t ON t.id = q.tenant_id\n WHERE (p_statuses IS NULL OR q.status = ANY(p_statuses))\n AND (p_search IS NULL OR p_search = ''\n OR q.subject ILIKE '%'||p_search||'%'\n OR q.description ILIKE '%'||p_search||'%'\n OR t.name ILIKE '%'||p_search||'%')\n ORDER BY\n CASE q.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END,\n q.updated_at DESC\n ) r;\n RETURN result;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_requests(text[], text) TO authenticated;\n\n-- \u2500\u2500 read: one request in full \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_detail(p_request_id uuid)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE result jsonb;\nBEGIN\n PERFORM public.support_console_guard();\n SELECT jsonb_build_object(\n 'request', (SELECT row_to_json(x) FROM (\n SELECT q.*, t.name AS tenant_name, t.slug AS tenant_slug,\n public._support_user_name(q.created_by, q.tenant_id) AS created_by_name\n FROM public.plg_support_requests q\n LEFT JOIN public.tenants t ON t.id = q.tenant_id\n WHERE q.id = p_request_id) x),\n 'messages', (SELECT COALESCE(jsonb_agg(row_to_json(m) ORDER BY m.created_at), '[]'::jsonb)\n FROM public.plg_support_request_messages m WHERE m.request_id = p_request_id),\n 'events', (SELECT COALESCE(jsonb_agg(row_to_json(e) ORDER BY e.created_at), '[]'::jsonb)\n FROM public.plg_support_request_events e WHERE e.request_id = p_request_id),\n 'attachments', (SELECT COALESCE(jsonb_agg(row_to_json(a) ORDER BY a.created_at), '[]'::jsonb)\n FROM public.plg_support_request_attachments a WHERE a.request_id = p_request_id)\n ) INTO result;\n RETURN result;\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_detail(uuid) TO authenticated;\n\n-- Internal: drop one unread notice to the requester, in their own tenant.\nCREATE OR REPLACE FUNCTION public._support_notify(p_request_id uuid, p_kind text)\nRETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid; v_user uuid;\nBEGIN\n SELECT tenant_id, created_by INTO v_tenant, v_user\n FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_user IS NULL THEN RETURN; END IF;\n INSERT INTO public.plg_support_notifications (tenant_id, request_id, user_id, kind)\n VALUES (v_tenant, p_request_id, v_user, p_kind);\nEND;\n$fn$;\n\n-- \u2500\u2500 write: operator changes status (and the client hears about it) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_set_status(\n p_request_id uuid, p_status text, p_note text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid;\nBEGIN\n PERFORM public.support_console_guard();\n IF p_status NOT IN ('received','in_service','waiting_you','in_development','resolved_check','closed','cancelled') THEN\n RAISE EXCEPTION 'invalid status %', p_status USING ERRCODE = '22023';\n END IF;\n SELECT tenant_id INTO v_tenant FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'request not found' USING ERRCODE = 'P0002'; END IF;\n UPDATE public.plg_support_requests\n SET status = p_status,\n resolved_at = CASE WHEN p_status IN ('resolved_check','closed') THEN now() ELSE resolved_at END\n WHERE id = p_request_id;\n INSERT INTO public.plg_support_request_events (tenant_id, request_id, type, note)\n VALUES (v_tenant, p_request_id, 'status_changed', p_status);\n PERFORM public._support_notify(p_request_id,\n CASE WHEN p_status IN ('waiting_you','resolved_check') THEN 'waiting_you'\n WHEN p_status IN ('closed') THEN 'resolved' ELSE 'reply' END);\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_set_status(uuid, text, text) TO authenticated;\n\n-- \u2500\u2500 write: operator sets priority \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_set_priority(p_request_id uuid, p_priority text)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n PERFORM public.support_console_guard();\n IF p_priority NOT IN ('low','normal','high','urgent') THEN\n RAISE EXCEPTION 'invalid priority %', p_priority USING ERRCODE = '22023';\n END IF;\n UPDATE public.plg_support_requests SET priority = p_priority WHERE id = p_request_id;\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_set_priority(uuid, text) TO authenticated;\n\n-- \u2500\u2500 write: operator replies to the requester \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_reply(p_request_id uuid, p_body text)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nDECLARE v_tenant uuid;\nBEGIN\n PERFORM public.support_console_guard();\n IF p_body IS NULL OR btrim(p_body) = '' THEN RAISE EXCEPTION 'empty message' USING ERRCODE = '22023'; END IF;\n SELECT tenant_id INTO v_tenant FROM public.plg_support_requests WHERE id = p_request_id;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'request not found' USING ERRCODE = 'P0002'; END IF;\n INSERT INTO public.plg_support_request_messages (tenant_id, request_id, author_kind, author_id, body)\n VALUES (v_tenant, p_request_id, 'team', auth.uid(), btrim(p_body));\n PERFORM public._support_notify(p_request_id, 'reply');\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_reply(uuid, text) TO authenticated;\n\n-- \u2500\u2500 write: operator links a CRM lead/client \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.support_console_link_lead(\n p_request_id uuid, p_lead_id uuid, p_lead_name text DEFAULT NULL\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $fn$\nBEGIN\n PERFORM public.support_console_guard();\n UPDATE public.plg_support_requests\n SET linked_lead_id = p_lead_id, linked_lead_name = p_lead_name\n WHERE id = p_request_id;\n RETURN public.support_console_detail(p_request_id);\nEND;\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_console_link_lead(uuid, uuid, text) TO authenticated;\n";
|
|
3
|
+
export declare const MIGRATION_002_OPERATOR_ATTACHMENT_READ = "-- plugin-support \u2014 let a helpdesk OPERATOR open a chamado's attachments.\n-- Idempotent / replay-safe.\n--\n-- The bucket's read policy (000_baseline) scopes every object to its owning\n-- tenant: (storage.foldername(name))[1] IN user_tenant_ids(). That is right for\n-- the store, but the GoControl operator is in ANOTHER tenant, so the signed URL\n-- never comes back and the console shows \"anexo\" instead of the image. This adds\n-- a SECOND, permissive SELECT policy (RLS OR-combines them, so the tenant rule is\n-- untouched) that lets an operator read any support-attachments object \u2014 gated by\n-- the same support_is_operator() that guards the whole console. Read only: upload,\n-- update and delete stay tenant-scoped.\nDROP POLICY IF EXISTS plg_support_attachments_operator_read ON storage.objects;\nCREATE POLICY plg_support_attachments_operator_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments' AND public.support_is_operator());\n";
|
|
4
|
+
export declare const MIGRATION_003_OPERATOR_ATTACHMENT_READ_FIX = "-- plugin-support \u2014 make the operator attachment policy actually work from Storage.\n-- Idempotent / replay-safe.\n--\n-- 002 gated the operator read on support_is_operator(), which asks\n-- app.current_tenant_id() \u2014 the OPEN workspace. That GUC is set on the normal\n-- data API, but NOT on a Storage sign request, so inside the bucket policy it is\n-- null and the check fails: the operator still got a 400 and saw \"anexo\".\n--\n-- This swaps to a context-independent test that leans only on auth.uid() (always\n-- present from the JWT): the user is a seeded cross-tenant operator, OR an active\n-- member of a tenant that runs the console (the facet). Same intent as\n-- support_is_operator(), minus the current-workspace scoping that Storage cannot\n-- answer. SECURITY DEFINER so the lookups bypass RLS, like the original.\nCREATE OR REPLACE FUNCTION public.support_is_operator_global()\nRETURNS boolean\nLANGUAGE sql STABLE SECURITY DEFINER SET search_path TO ''\nAS $fn$\n SELECT\n EXISTS (SELECT 1 FROM public.plg_support_operators o WHERE o.user_id = auth.uid())\n OR EXISTS (\n SELECT 1\n FROM app.tenant_plugins tp\n JOIN app.memberships m\n ON m.tenant_id = tp.tenant_id AND m.user_id = auth.uid() AND m.active\n WHERE tp.plugin_id = 'support' AND tp.facet = 'console' AND tp.status = 'active'\n );\n$fn$;\nGRANT EXECUTE ON FUNCTION public.support_is_operator_global() TO authenticated;\n\nDROP POLICY IF EXISTS plg_support_attachments_operator_read ON storage.objects;\nCREATE POLICY plg_support_attachments_operator_read ON storage.objects\n FOR SELECT TO authenticated\n USING (bucket_id = 'support-attachments' AND public.support_is_operator_global());\n";
|
|
5
|
+
export declare const MIGRATIONS: Array<{
|
|
6
|
+
id: string;
|
|
7
|
+
sql: string;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,qtZAkNlC,CAAA;AAED,eAAO,MAAM,sBAAsB,wrbAgQlC,CAAA;AAED,eAAO,MAAM,sCAAsC,i/BAelD,CAAA;AAED,eAAO,MAAM,0CAA0C,8rDAiCtD,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAKzD,CAAA"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SupportRequest, SupportRequestDetail, SupportScope, SupportFilter, CreateSupportRequestInput, PendingAttachment } from './types';
|
|
2
|
+
interface SupportState {
|
|
3
|
+
requests: SupportRequest[];
|
|
4
|
+
loadingList: boolean;
|
|
5
|
+
scope: SupportScope;
|
|
6
|
+
filter: SupportFilter;
|
|
7
|
+
selectedId: string | null;
|
|
8
|
+
detail: SupportRequestDetail | null;
|
|
9
|
+
loadingDetail: boolean;
|
|
10
|
+
unread: number;
|
|
11
|
+
setScope: (scope: SupportScope) => void;
|
|
12
|
+
setFilter: (filter: SupportFilter) => void;
|
|
13
|
+
loadList: () => Promise<void>;
|
|
14
|
+
select: (id: string | null) => Promise<void>;
|
|
15
|
+
refreshDetail: () => Promise<void>;
|
|
16
|
+
refreshUnread: () => Promise<void>;
|
|
17
|
+
create: (input: CreateSupportRequestInput, attachments: PendingAttachment[]) => Promise<{
|
|
18
|
+
id: string;
|
|
19
|
+
number: number;
|
|
20
|
+
}>;
|
|
21
|
+
sendMessage: (body: string) => Promise<void>;
|
|
22
|
+
cancel: () => Promise<void>;
|
|
23
|
+
validate: (ok: boolean, reason?: string) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare const useSupportStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SupportState>>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAAE,oBAAoB,EAAE,YAAY,EAAE,aAAa,EACjE,yBAAyB,EAAE,iBAAiB,EAC7C,MAAM,SAAS,CAAA;AAGhB,UAAU,YAAY;IACpB,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,WAAW,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,aAAa,CAAA;IAErB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACnC,aAAa,EAAE,OAAO,CAAA;IAEtB,MAAM,EAAE,MAAM,CAAA;IAEd,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACvC,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IAC1C,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAElC,MAAM,EAAE,CAAC,KAAK,EAAE,yBAAyB,EAAE,WAAW,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,eAAO,MAAM,eAAe,2EAoFzB,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type SupportKind = 'problem' | 'improvement' | 'other';
|
|
2
|
+
export type SupportPriority = 'low' | 'normal' | 'high' | 'urgent';
|
|
3
|
+
export type SupportStatus = 'received' | 'in_service' | 'waiting_you' | 'in_development' | 'resolved_check' | 'closed' | 'cancelled';
|
|
4
|
+
export type SupportEventType = 'opened' | 'status_changed' | 'validated_ok' | 'reopened' | 'cancelled' | 'closed_no_reply' | 'merged';
|
|
5
|
+
export type SupportMessageAuthor = 'user' | 'team';
|
|
6
|
+
export interface SupportRequest {
|
|
7
|
+
id: string;
|
|
8
|
+
tenantId: string;
|
|
9
|
+
number: number;
|
|
10
|
+
kind: SupportKind;
|
|
11
|
+
subject: string | null;
|
|
12
|
+
description: string;
|
|
13
|
+
status: SupportStatus;
|
|
14
|
+
route: string | null;
|
|
15
|
+
unitId: string | null;
|
|
16
|
+
screenSnapshot: Record<string, unknown>;
|
|
17
|
+
originConversationId: string | null;
|
|
18
|
+
area: string | null;
|
|
19
|
+
createdBy: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
resolvedAt: string | null;
|
|
23
|
+
metadata: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface SupportMessage {
|
|
26
|
+
id: string;
|
|
27
|
+
requestId: string;
|
|
28
|
+
authorKind: SupportMessageAuthor;
|
|
29
|
+
authorId: string | null;
|
|
30
|
+
body: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SupportEvent {
|
|
34
|
+
id: string;
|
|
35
|
+
requestId: string;
|
|
36
|
+
type: SupportEventType;
|
|
37
|
+
note: string | null;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
}
|
|
40
|
+
export interface SupportAttachment {
|
|
41
|
+
id: string;
|
|
42
|
+
requestId: string;
|
|
43
|
+
storagePath: string;
|
|
44
|
+
name: string;
|
|
45
|
+
sizeBytes: number | null;
|
|
46
|
+
contentType: string | null;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SupportRequestDetail extends SupportRequest {
|
|
50
|
+
messages: SupportMessage[];
|
|
51
|
+
events: SupportEvent[];
|
|
52
|
+
attachments: SupportAttachment[];
|
|
53
|
+
}
|
|
54
|
+
/** What the form collects before an attachment upload. */
|
|
55
|
+
export interface CreateSupportRequestInput {
|
|
56
|
+
kind: SupportKind;
|
|
57
|
+
description: string;
|
|
58
|
+
subject?: string | null;
|
|
59
|
+
route?: string | null;
|
|
60
|
+
unitId?: string | null;
|
|
61
|
+
screenSnapshot?: Record<string, unknown>;
|
|
62
|
+
originConversationId?: string | null;
|
|
63
|
+
area?: string | null;
|
|
64
|
+
}
|
|
65
|
+
/** A file staged in the form, before it is uploaded to the bucket. */
|
|
66
|
+
export interface PendingAttachment {
|
|
67
|
+
id: string;
|
|
68
|
+
file: Blob;
|
|
69
|
+
name: string;
|
|
70
|
+
contentType: string;
|
|
71
|
+
sizeBytes: number;
|
|
72
|
+
/** Object URL for a local thumbnail; revoke on unmount. */
|
|
73
|
+
previewUrl?: string;
|
|
74
|
+
}
|
|
75
|
+
export type SupportScope = 'mine' | 'all';
|
|
76
|
+
export type SupportFilter = 'waiting' | 'in_progress' | 'closed' | 'all';
|
|
77
|
+
export declare const MAX_ATTACHMENTS = 8;
|
|
78
|
+
export declare const MAX_ATTACHMENT_BYTES: number;
|
|
79
|
+
export declare const SUPPORT_BUCKET = "support-attachments";
|
|
80
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The GoControl helpdesk console (Zendesk/Intercom-style). An operator reads and
|
|
4
|
+
* acts on the support requests of EVERY tenant through the support_console_* RPCs.
|
|
5
|
+
* The triage list lives at #/support/console; clicking a card opens the ticket as
|
|
6
|
+
* its own nested page (#/support/console/<id>) with a "← Voltar" header.
|
|
7
|
+
*/
|
|
8
|
+
export declare function HelpdeskConsole(): React.JSX.Element;
|
|
9
|
+
export default HelpdeskConsole;
|
|
10
|
+
//# sourceMappingURL=HelpdeskConsole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HelpdeskConsole.d.ts","sourceRoot":"","sources":["../../src/views/HelpdeskConsole.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAyCzB;;;;;GAKG;AACH,wBAAgB,eAAe,sBAe9B;AAyRD,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ScreenCaptureEditorProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
onUse: (file: File, previewUrl: string) => void;
|
|
6
|
+
/** When set, skip acquisition and edit this file directly (the "Arquivo"/paste path). */
|
|
7
|
+
initialFile?: File | null;
|
|
8
|
+
/** Start capture immediately in this mode when the editor opens. */
|
|
9
|
+
autoStart?: 'tab' | 'window' | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function ScreenCaptureEditor({ open, onClose, onUse, initialFile, autoStart }: ScreenCaptureEditorProps): React.JSX.Element;
|
|
12
|
+
//# sourceMappingURL=ScreenCaptureEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScreenCaptureEditor.d.ts","sourceRoot":"","sources":["../../src/views/ScreenCaptureEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAWzB,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,yFAAyF;IACzF,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACzB,oEAAoE;IACpE,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAA;CACpC;AAID,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,wBAAwB,qBA6J7G"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type FaqItem, type WhatsNewItem, type StatusComponent } from '../lib/content';
|
|
3
|
+
export interface SupportCenterContent {
|
|
4
|
+
faq: FaqItem[];
|
|
5
|
+
whatsNew: WhatsNewItem[];
|
|
6
|
+
statusComponents: StatusComponent[];
|
|
7
|
+
}
|
|
8
|
+
/** Router seam: the same /support route renders either the help center or a
|
|
9
|
+
* full-page ticket. Kept thin so hook order never changes between the two. */
|
|
10
|
+
export declare function SupportCenter({ content }: {
|
|
11
|
+
content: SupportCenterContent;
|
|
12
|
+
}): React.JSX.Element;
|
|
13
|
+
export default SupportCenter;
|
|
14
|
+
//# sourceMappingURL=SupportCenter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupportCenter.d.ts","sourceRoot":"","sources":["../../src/views/SupportCenter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAc,MAAM,gBAAgB,CAAA;AAIlG,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,OAAO,EAAE,CAAA;IACd,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,gBAAgB,EAAE,eAAe,EAAE,CAAA;CACpC;AAoBD;+EAC+E;AAC/E,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,oBAAoB,CAAA;CAAE,qBAG3E;AAkPD,eAAe,aAAa,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SupportKind } from '../types';
|
|
3
|
+
export interface SupportMenuProps {
|
|
4
|
+
onPickKind: (kind: SupportKind) => void;
|
|
5
|
+
/** "Tirar dúvidas" — opens the Dico chat. Hidden when not wired (e.g. on the page). */
|
|
6
|
+
onAskDico?: () => void;
|
|
7
|
+
/** Footer entry. Hidden when already on the list. */
|
|
8
|
+
onOpenList?: () => void;
|
|
9
|
+
unread?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function SupportMenu({ onPickKind, onAskDico, onOpenList, unread }: SupportMenuProps): React.JSX.Element;
|
|
12
|
+
//# sourceMappingURL=SupportMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupportMenu.d.ts","sourceRoot":"","sources":["../../src/views/SupportMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAqBD,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAU,EAAE,EAAE,gBAAgB,qBAuB9F"}
|