diff --git a/404.html b/404.html index 605a04fe7..010a77a12 100644 --- a/404.html +++ b/404.html @@ -13,7 +13,7 @@ - +
diff --git a/assets/images/localhost_3500_resource_audit_logs-8996765a6d534b48c3e52b9949e4cc89.png b/assets/images/localhost_3500_resource_audit_logs-8996765a6d534b48c3e52b9949e4cc89.png new file mode 100644 index 000000000..c7c0f42ef Binary files /dev/null and b/assets/images/localhost_3500_resource_audit_logs-8996765a6d534b48c3e52b9949e4cc89.png differ diff --git a/assets/js/0058754d.531c2617.js b/assets/js/0058754d.5a65823b.js similarity index 96% rename from assets/js/0058754d.531c2617.js rename to assets/js/0058754d.5a65823b.js index 1ab407dd4..965a34e83 100644 --- a/assets/js/0058754d.531c2617.js +++ b/assets/js/0058754d.5a65823b.js @@ -1 +1 @@ -"use strict";(self.webpackChunkadminforth=self.webpackChunkadminforth||[]).push([[3800],{2681:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>c});var s=i(4848),t=i(8453);const o={},r=void 0,l={id:"tutorial/Plugins/ForeignInlineList",title:"ForeignInlineList",description:"Foreign inline list plugin allows to display a list (table) of items from a foreign table in the show view.",source:"@site/docs/tutorial/Plugins/ForeignInlineList.md",sourceDirName:"tutorial/Plugins",slug:"/tutorial/Plugins/ForeignInlineList",permalink:"/docs/tutorial/Plugins/ForeignInlineList",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"AuditLog",permalink:"/docs/tutorial/Plugins/AuditLog"}},a={},c=[{value:"Usage",id:"usage",level:2}];function u(e){const n={a:"a",code:"code",h2:"h2",img:"img",p:"p",pre:"pre",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"Foreign inline list plugin allows to display a list (table) of items from a foreign table in the show view."}),"\n",(0,s.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,s.jsx)(n.p,{children:"Import plugin:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"import ForeignInlineListPlugin from 'adminforth/plugins/ForeignInlineListPlugin';\n"})}),"\n",(0,s.jsx)(n.p,{children:"If you are using pure Node without TypeScript, you can use the following code:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"import ForeignInlineListPlugin from 'adminforth/dist/plugins/ForeignInlineListPlugin/index.js';\n"})}),"\n",(0,s.jsxs)(n.p,{children:["In ",(0,s.jsx)(n.a,{href:"/docs/tutorial/gettingStarted",children:"Getting Started"})," we created a ",(0,s.jsx)(n.code,{children:"'aparts'"})," resource which has a field ",(0,s.jsx)(n.code,{children:"'realtor_id'"}),".\nThis field refers to record from ",(0,s.jsx)(n.code,{children:"'users'"})," resource. This means that we can display a list of appartments in the user's show view."]}),"\n",(0,s.jsxs)(n.p,{children:["Add to your ",(0,s.jsx)(n.code,{children:"'users'"})," resource configuration (which we created in ), plugin instance:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{ \n ...\n resourceId: 'users',\n ...\n plugins: [\n new ForeignInlineListPlugin({\n foreignResourceId: 'aparts',\n modifyTableResourceConfig: (resourceConfig: AdminForthResource) => {\n // hide column 'square_meter' from both 'list' and 'filter'\n const column = resourceConfig.columns.find((c: AdminForthResourceColumn) => c.name === 'square_meter')!.showIn = [];\n resourceConfig.options!.listPageSize = 1;\n\n // feel free to console.log and edit resourceConfig as you need\n },\n }),\n ],\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You can use ",(0,s.jsx)(n.code,{children:"modifyTableResourceConfig"})," callback to modify what columns to show in the list and filter of the foreign table."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"alt text",src:i(9742).A+"",width:"3670",height:"2044"})}),"\n",(0,s.jsxs)(n.p,{children:["See ",(0,s.jsx)(n.a,{href:"/docs/api/plugins/ForeignInlineListPlugin/types/type-aliases/PluginOptions",children:"API Reference"})," for more all options."]})]})}function d(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},9742:(e,n,i)=>{i.d(n,{A:()=>s});const s=i.p+"assets/images/localhost_3500_resource_users_show_08dpfh-a8f5bd63bb09e071c67a13d7121e95fd.png"},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>l});var s=i(6540);const t={},o=s.createContext(t);function r(e){const n=s.useContext(o);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkadminforth=self.webpackChunkadminforth||[]).push([[3800],{2681:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>c});var s=i(4848),t=i(8453);const o={},r=void 0,l={id:"tutorial/Plugins/ForeignInlineList",title:"ForeignInlineList",description:"Foreign inline list plugin allows to display a list (table) of items from a foreign table in the show view.",source:"@site/docs/tutorial/Plugins/ForeignInlineList.md",sourceDirName:"tutorial/Plugins",slug:"/tutorial/Plugins/ForeignInlineList",permalink:"/docs/tutorial/Plugins/ForeignInlineList",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"AuditLog",permalink:"/docs/tutorial/Plugins/AuditLog"}},a={},c=[{value:"Usage",id:"usage",level:2}];function u(e){const n={a:"a",code:"code",h2:"h2",img:"img",p:"p",pre:"pre",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"Foreign inline list plugin allows to display a list (table) of items from a foreign table in the show view."}),"\n",(0,s.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,s.jsx)(n.p,{children:"Import plugin:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"import ForeignInlineListPlugin from 'adminforth/plugins/ForeignInlineListPlugin';\n"})}),"\n",(0,s.jsx)(n.p,{children:"If you are using pure Node without TypeScript, you can use the following code:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"import ForeignInlineListPlugin from 'adminforth/dist/plugins/ForeignInlineListPlugin/index.js';\n"})}),"\n",(0,s.jsxs)(n.p,{children:["In ",(0,s.jsx)(n.a,{href:"/docs/tutorial/gettingStarted",children:"Getting Started"})," we created a ",(0,s.jsx)(n.code,{children:"'aparts'"})," resource which has a field ",(0,s.jsx)(n.code,{children:"'realtor_id'"}),".\nThis field refers to record from ",(0,s.jsx)(n.code,{children:"'users'"})," resource. This means that we can display a list of appartments in the user's show view."]}),"\n",(0,s.jsxs)(n.p,{children:["Add to your ",(0,s.jsx)(n.code,{children:"'users'"})," resource configuration (which we created in ), plugin instance:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"{ \n ...\n resourceId: 'users',\n ...\n plugins: [\n new ForeignInlineListPlugin({\n foreignResourceId: 'aparts',\n modifyTableResourceConfig: (resourceConfig: AdminForthResource) => {\n // hide column 'square_meter' from both 'list' and 'filter'\n const column = resourceConfig.columns.find((c: AdminForthResourceColumn) => c.name === 'square_meter')!.showIn = [];\n resourceConfig.options!.listPageSize = 1;\n\n // feel free to console.log and edit resourceConfig as you need\n },\n }),\n ],\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You can use ",(0,s.jsx)(n.code,{children:"modifyTableResourceConfig"})," callback to modify what columns to show in the list and filter of the foreign table."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"alt text",src:i(3986).A+"",width:"3670",height:"2044"})}),"\n",(0,s.jsxs)(n.p,{children:["See ",(0,s.jsx)(n.a,{href:"/docs/api/plugins/ForeignInlineListPlugin/types/type-aliases/PluginOptions",children:"API Reference"})," for more all options."]})]})}function d(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},3986:(e,n,i)=>{i.d(n,{A:()=>s});const s=i.p+"assets/images/localhost_3500_resource_users_show_08dpfh-a8f5bd63bb09e071c67a13d7121e95fd.png"},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>l});var s=i(6540);const t={},o=s.createContext(t);function r(e){const n=s.useContext(o);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/024de627.c84afb60.js b/assets/js/024de627.8360fc62.js similarity index 58% rename from assets/js/024de627.c84afb60.js rename to assets/js/024de627.8360fc62.js index 56fc40a45..439036455 100644 --- a/assets/js/024de627.c84afb60.js +++ b/assets/js/024de627.8360fc62.js @@ -1 +1 @@ -"use strict";(self.webpackChunkadminforth=self.webpackChunkadminforth||[]).push([[7865],{5191:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>a,toc:()=>c});var o=n(4848),s=n(8453);const i={},r="Hooks",a={id:"tutorial/Customization/hooks",title:"Hooks",description:"Hooks are used to:",source:"@site/docs/tutorial/03-Customization/04-hooks.md",sourceDirName:"tutorial/03-Customization",slug:"/tutorial/Customization/hooks",permalink:"/docs/tutorial/Customization/hooks",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Virtual columns",permalink:"/docs/tutorial/Customization/virtualColumns"},next:{title:"Disabling actions",permalink:"/docs/tutorial/Customization/limitingAccess"}},d={},c=[{value:"Modify the data before it is saved to the database",id:"modify-the-data-before-it-is-saved-to-the-database",level:2}];function l(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.h1,{id:"hooks",children:"Hooks"}),"\n",(0,o.jsx)(t.p,{children:"Hooks are used to:"}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsx)(t.li,{children:"modify the data before it is saved to the database on create or update"}),"\n",(0,o.jsx)(t.li,{children:"execute something after data were saved or deleted"}),"\n",(0,o.jsx)(t.li,{children:"change the query before fetching items from the database"}),"\n",(0,o.jsx)(t.li,{children:"modify the fetched data before it is displayed in the list and show"}),"\n",(0,o.jsxs)(t.li,{children:["prevent the request to db depending on some condition (Better use ",(0,o.jsx)(t.a,{href:"#limiting-access-to-the-resource-actions",children:"allowedActions"})," for this)"]}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"modify-the-data-before-it-is-saved-to-the-database",children:"Modify the data before it is saved to the database"}),"\n",(0,o.jsxs)(t.p,{children:["Let's add reference to ",(0,o.jsx)(t.code,{children:"adminUser"})," when user creates a new apartment:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",metastring:"title='./index.ts'",children:"// diff-add\nimport type { AdminUser } from 'adminforth/types/AdminForthConfig.js';\n\n{\n ...\n resourceId: 'aparts',\n columns: [\n ...\n {\n name: 'realtor_id',\n ...\n+ showIn: ['list', 'show', 'edit'], // don't even show this field in create\n ...\n },\n ...\n ],\n ...\n+ hooks: {\n+ create: {\n+ beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, record: any }) => {\n+ if (adminUser.isRoot) {\n+ return { ok: false, error: \"Root user can't create appartment, relogin as DB user\" };\n+ }\n+ record.realtor_id = adminUser.dbUser.id;\n+ return { ok: true, record };\n+ }\n+ }\n+ }\n}\n"})})]})}function u(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>r,x:()=>a});var o=n(6540);const s={},i=o.createContext(s);function r(e){const t=o.useContext(i);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkadminforth=self.webpackChunkadminforth||[]).push([[7865],{5191:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>r,contentTitle:()=>a,default:()=>f,frontMatter:()=>s,metadata:()=>d,toc:()=>c});var o=n(4848),i=n(8453);const s={},a="Hooks",d={id:"tutorial/Customization/hooks",title:"Hooks",description:"Hooks are used to:",source:"@site/docs/tutorial/03-Customization/04-hooks.md",sourceDirName:"tutorial/03-Customization",slug:"/tutorial/Customization/hooks",permalink:"/docs/tutorial/Customization/hooks",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Virtual columns",permalink:"/docs/tutorial/Customization/virtualColumns"},next:{title:"Disabling actions",permalink:"/docs/tutorial/Customization/limitingAccess"}},r={},c=[{value:"Modify the data before it is saved to the database",id:"modify-the-data-before-it-is-saved-to-the-database",level:2}];function l(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.h1,{id:"hooks",children:"Hooks"}),"\n",(0,o.jsx)(t.p,{children:"Hooks are used to:"}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsx)(t.li,{children:"modify the data before it is saved to the database on create or update"}),"\n",(0,o.jsx)(t.li,{children:"execute something after data were saved or deleted"}),"\n",(0,o.jsx)(t.li,{children:"change the query before fetching items from the database"}),"\n",(0,o.jsx)(t.li,{children:"modify the fetched data before it is displayed in the list and show"}),"\n",(0,o.jsxs)(t.li,{children:["prevent the request to db depending on some condition (Better use ",(0,o.jsx)(t.a,{href:"#limiting-access-to-the-resource-actions",children:"allowedActions"})," for this)"]}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"modify-the-data-before-it-is-saved-to-the-database",children:"Modify the data before it is saved to the database"}),"\n",(0,o.jsxs)(t.p,{children:["Let's add reference to ",(0,o.jsx)(t.code,{children:"adminUser"})," when user creates a new apartment:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-ts",metastring:"title='./index.ts'",children:"// diff-add\nimport type { AdminUser } from 'adminforth/types/AdminForthConfig.js';\n\n{\n ...\n resourceId: 'aparts',\n columns: [\n ...\n {\n name: 'realtor_id',\n ...\n//diff-add\n showIn: ['list', 'show', 'edit'], // don't even show this field in create\n ...\n },\n ...\n ],\n ...\n//diff-add\n hooks: {\n//diff-add\n create: {\n//diff-add\n beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, record: any }) => {\n//diff-add\n if (adminUser.isRoot) {\n//diff-add\n return { ok: false, error: \"Root user can't create appartment, relogin as DB user\" };\n//diff-add\n }\n//diff-add\n record.realtor_id = adminUser.dbUser.id;\n//diff-add\n return { ok: true, record };\n//diff-add\n }\n//diff-add\n }\n//diff-add\n }\n}\n"})})]})}function f(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>d});var o=n(6540);const i={},s=o.createContext(i);function a(e){const t=o.useContext(s);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),o.createElement(s.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0f6f7c87.a199deca.js b/assets/js/0f6f7c87.a199deca.js deleted file mode 100644 index 0c3fe881d..000000000 --- a/assets/js/0f6f7c87.a199deca.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkadminforth=self.webpackChunkadminforth||[]).push([[1768],{9706:(n,e,t)=>{t.r(e),t.d(e,{assets:()=>c,contentTitle:()=>r,default:()=>u,frontMatter:()=>s,metadata:()=>i,toc:()=>d});var o=t(4848),a=t(8453);const s={},r="Page Injections",i={id:"tutorial/Customization/pageInjections",title:"Page Injections",description:"In addition to ability to create custom pages and overwrite how fields are rendered, you can also inject custom components in standard AdminForth page.",source:"@site/docs/tutorial/03-Customization/08-pageInjections.md",sourceDirName:"tutorial/03-Customization",slug:"/tutorial/Customization/pageInjections",permalink:"/docs/tutorial/Customization/pageInjections",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:8,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Alerts and confirmations",permalink:"/docs/tutorial/Customization/alert"},next:{title:"Custom bulk actions",permalink:"/docs/tutorial/Customization/bulkActions"}},c={},d=[];function l(n){const e={blockquote:"blockquote",code:"code",h1:"h1",img:"img",p:"p",pre:"pre",...(0,a.R)(),...n.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(e.h1,{id:"page-injections",children:"Page Injections"}),"\n",(0,o.jsx)(e.p,{children:"In addition to ability to create custom pages and overwrite how fields are rendered, you can also inject custom components in standard AdminForth page."}),"\n",(0,o.jsxs)(e.p,{children:["For example let's add a custom pie chart to the ",(0,o.jsx)(e.code,{children:"list"})," page of the ",(0,o.jsx)(e.code,{children:"aparts"})," resource. Pie chart will show the distribution of the rooms count and more over will allow to filter the list by the rooms count."]}),"\n",(0,o.jsx)(e.pre,{children:(0,o.jsx)(e.code,{className:"language-ts",metastring:'title="/index.ts"',children:"{\n resourceId: 'aparts',\n ...\n//diff-add\n options: {\n//diff-add\n pageInjections: {\n//diff-add\n list: {\n//diff-add\n afterBreadcrumbs: '@@/ApartsPie.vue',\n//diff-add\n }\n//diff-add\n } \n//diff-add\n }\n}\n"})}),"\n",(0,o.jsxs)(e.p,{children:["Now create file ",(0,o.jsx)(e.code,{children:"ApartsPie.vue"})," in the ",(0,o.jsx)(e.code,{children:"custom"})," folder of your project:"]}),"\n",(0,o.jsx)(e.pre,{children:(0,o.jsx)(e.code,{className:"language-html",metastring:'title="/custom/ApartsPie.vue"',children:'\nInstall CHart.js library into your main package (near index.ts
):
npm install apexcharts --save
Create a Vue component in the custom
directory of your project, e.g. Dashboard.vue
:
<template>
<div class="px-4 py-8 bg-blue-50 dark:bg-gray-900 dark:shadow-none h-screen">
<h1 class="mb-4 text-xl font-extrabold text-gray-900 dark:text-white md:text-2xl lg:text-3xl"><span
class="text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400">Appartments</span>
Statistics.</h1>
<div class="grid grid-cols-3 gap-4">
<div class="max-w-md w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6" v-if="data">
<div class="flex justify-between">
<div>
<h5 class="leading-none text-3xl font-bold text-gray-900 dark:text-white pb-2">{{ data.totalAparts }}</h5>
<p class="text-base font-normal text-gray-500 dark:text-gray-400">Apartments last 7 days</p>
</div>
</div>
<div id="area-chart"></div>
</div>
<div class="w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6 row-span-2 col-span-2" v-if="data">
<div class="grid grid-cols-2 py-3">
<dl>
<dt class="text-base font-normal text-gray-500 dark:text-gray-400 pb-1">Listed price</dt>
<dd class="leading-none text-xl font-bold text-green-500 dark:text-green-400">{{
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
data.totalListedPrice,
) }}
</dd>
</dl>
<dl>
<dt class="text-base font-normal text-gray-500 dark:text-gray-400 pb-1">Unlisted price</dt>
<dd class="leading-none text-xl font-bold text-red-600 dark:text-red-500">{{
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
data.totalUnlistedPrice,
) }}
</dd>
</dl>
</div>
<div id="bar-chart"></div>
</div>
<div class="max-w-md w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6" v-if="data">
<div class="flex justify-between mb-5">
<div>
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
Unlisted vs Listed price
</p>
</div>
</div>
<div id="size-chart" class="[&>div]:mx-auto"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import ApexCharts from 'apexcharts';
import dayjs from 'dayjs';
const data = ref({});
const optionsC1 = {
chart: {
height: 145,
type: "area",
fontFamily: "Inter, sans-serif",
dropShadow: {
enabled: false,
},
toolbar: {
show: false,
},
},
tooltip: {
enabled: true,
x: {
show: false,
},
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: "#1C64F2",
gradientToColors: ["#1C64F2"],
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 6,
},
grid: {
show: false,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: 0
},
},
series: [
{
name: "Added appartments",
data: [],
color: "#1A56DB",
},
],
xaxis: {
categories: [],
labels: {
show: false,
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
show: false,
},
};
const optionsC2 = {
series: [
{
name: "Listed",
color: "#31C48D",
data: [],
},
{
name: "Unlisted",
data: [],
color: "#F05252",
}
],
chart: {
sparkline: {
enabled: false,
},
type: "bar",
width: "100%",
height: 400,
toolbar: {
show: false,
}
},
fill: {
opacity: 1,
},
plotOptions: {
bar: {
horizontal: true,
columnWidth: "100%",
borderRadiusApplication: "end",
borderRadius: 6,
dataLabels: {
position: "top",
},
},
},
legend: {
show: true,
position: "bottom",
},
dataLabels: {
enabled: false,
},
tooltip: {
shared: true,
intersect: false,
formatter: function (value) {
return value
},
},
xaxis: {
labels: {
show: true,
style: {
fontFamily: "Inter, sans-serif",
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
},
formatter: function (value) {
return value
}
},
categories: [],
axisTicks: {
show: false,
},
axisBorder: {
show: false,
},
},
yaxis: {
labels: {
show: true,
style: {
fontFamily: "Inter, sans-serif",
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
}
}
},
grid: {
show: true,
strokeDashArray: 4,
padding: {
left: 10,
right: 2,
// top: -20
},
},
fill: {
opacity: 1,
}
}
const optionsC3 = {
chart: {
height: 145,
type: "area",
fontFamily: "Inter, sans-serif",
dropShadow: {
enabled: false,
},
toolbar: {
show: false,
},
},
tooltip: {
enabled: true,
x: {
show: false,
},
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: "#1C64F2",
gradientToColors: ["#1C64F2"],
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 6,
},
grid: {
show: false,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: -26
},
},
series: [
{
name: "Listed Price",
data: [],
color: "#1A56DB",
},
{
name: "Unlisted Price",
data: [],
color: "#7E3BF2",
},
],
xaxis: {
categories: [],
labels: {
show: false,
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
show: false,
labels: {
formatter: function (value) {
return '$' + value;
}
}
},
}
onMounted(async () => {
// Fetch data from the API
// and set it to the chartData
try {
const resp = await fetch('/api/dashboard/');
if (resp.status === 401) {
// user will be redirected to login page automatically so no need to handle anything here
return;
}
data.value = await resp.json();
} catch (error) {
window.adminforth.alert({
message: `Error fetching data: ${error.message}`,
variant: 'danger',
timeout: 'unlimited'
});
}
const apartsByDaysReverse = data.value.apartsByDays.reverse();
optionsC1.series[0].data = apartsByDaysReverse.map((item) => item.count);
optionsC1.xaxis.categories = apartsByDaysReverse.map((item) => dayjs(item.day).format('DD MMM'));
const chart = new ApexCharts(document.getElementById("area-chart"), optionsC1);
chart.render();
optionsC2.series[0].data = data.value.listedVsUnlistedByDays.map((item) => item.listed);
optionsC2.series[1].data = data.value.listedVsUnlistedByDays.map((item) => item.unlisted);
optionsC2.xaxis.categories = data.value.listedVsUnlistedByDays.map((item) => dayjs(item.day).format('DD MMM'));
const chart2 = new ApexCharts(document.getElementById("bar-chart"), optionsC2);
chart2.render();
optionsC3.series[0].data = data.value.listedVsUnlistedPriceByDays.map((item) => item.listedPrice.toFixed(2));
optionsC3.series[1].data = data.value.listedVsUnlistedPriceByDays.map((item) => item.unlistedPrice.toFixed(2));
optionsC3.xaxis.categories = data.value.listedVsUnlistedPriceByDays.map((item) => dayjs(item.day).format('DD MMM'));
const chart3 = new ApexCharts(document.getElementById("size-chart"), optionsC3);
chart3.render();
})
</script>
<template>
<div class="px-4 py-8 bg-blue-50 dark:bg-gray-900 dark:shadow-none h-screen">
<h1 class="mb-4 text-xl font-extrabold text-gray-900 dark:text-white md:text-2xl lg:text-3xl"><span
class="text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400">Appartments</span>
Statistics.</h1>
<div class="grid grid-cols-3 gap-4">
<div class="max-w-md w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6" v-if="data">
<div class="flex justify-between">
<div>
<h5 class="leading-none text-3xl font-bold text-gray-900 dark:text-white pb-2">{{ data.totalAparts }}</h5>
<p class="text-base font-normal text-gray-500 dark:text-gray-400">Apartments last 7 days</p>
</div>
</div>
<div id="area-chart"></div>
</div>
<div class="w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6 row-span-2 col-span-2" v-if="data">
<div class="grid grid-cols-2 py-3">
<dl>
<dt class="text-base font-normal text-gray-500 dark:text-gray-400 pb-1">Listed price</dt>
<dd class="leading-none text-xl font-bold text-green-500 dark:text-green-400">{{
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
data.totalListedPrice,
) }}
</dd>
</dl>
<dl>
<dt class="text-base font-normal text-gray-500 dark:text-gray-400 pb-1">Unlisted price</dt>
<dd class="leading-none text-xl font-bold text-red-600 dark:text-red-500">{{
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
data.totalUnlistedPrice,
) }}
</dd>
</dl>
</div>
<div id="bar-chart"></div>
</div>
<div class="max-w-md w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-6" v-if="data">
<div class="flex justify-between mb-5">
<div>
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
Unlisted vs Listed price
</p>
</div>
</div>
<div id="size-chart" class="[&>div]:mx-auto"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import ApexCharts from 'apexcharts';
import dayjs from 'dayjs';
const data = ref({});
const optionsC1 = {
chart: {
height: 145,
type: "area",
fontFamily: "Inter, sans-serif",
dropShadow: {
enabled: false,
},
toolbar: {
show: false,
},
},
tooltip: {
enabled: true,
x: {
show: false,
},
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: "#1C64F2",
gradientToColors: ["#1C64F2"],
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 6,
},
grid: {
show: false,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: 0
},
},
series: [
{
name: "Added appartments",
data: [],
color: "#1A56DB",
},
],
xaxis: {
categories: [],
labels: {
show: false,
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
show: false,
},
};
const optionsC2 = {
series: [
{
name: "Listed",
color: "#31C48D",
data: [],
},
{
name: "Unlisted",
data: [],
color: "#F05252",
}
],
chart: {
sparkline: {
enabled: false,
},
type: "bar",
width: "100%",
height: 400,
toolbar: {
show: false,
}
},
fill: {
opacity: 1,
},
plotOptions: {
bar: {
horizontal: true,
columnWidth: "100%",
borderRadiusApplication: "end",
borderRadius: 6,
dataLabels: {
position: "top",
},
},
},
legend: {
show: true,
position: "bottom",
},
dataLabels: {
enabled: false,
},
tooltip: {
shared: true,
intersect: false,
formatter: function (value) {
return value
},
},
xaxis: {
labels: {
show: true,
style: {
fontFamily: "Inter, sans-serif",
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
},
formatter: function (value) {
return value
}
},
categories: [],
axisTicks: {
show: false,
},
axisBorder: {
show: false,
},
},
yaxis: {
labels: {
show: true,
style: {
fontFamily: "Inter, sans-serif",
cssClass: 'text-xs font-normal fill-gray-500 dark:fill-gray-400'
}
}
},
grid: {
show: true,
strokeDashArray: 4,
padding: {
left: 10,
right: 2,
// top: -20
},
},
fill: {
opacity: 1,
}
}
const optionsC3 = {
chart: {
height: 145,
type: "area",
fontFamily: "Inter, sans-serif",
dropShadow: {
enabled: false,
},
toolbar: {
show: false,
},
},
tooltip: {
enabled: true,
x: {
show: false,
},
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: "#1C64F2",
gradientToColors: ["#1C64F2"],
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 6,
},
grid: {
show: false,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: -26
},
},
series: [
{
name: "Listed Price",
data: [],
color: "#1A56DB",
},
{
name: "Unlisted Price",
data: [],
color: "#7E3BF2",
},
],
xaxis: {
categories: [],
labels: {
show: false,
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
yaxis: {
show: false,
labels: {
formatter: function (value) {
return '$' + value;
}
}
},
}
onMounted(async () => {
// Fetch data from the API
// and set it to the chartData
try {
const resp = await fetch('/api/dashboard/');
if (resp.status === 401) {
// user will be redirected to login page automatically so no need to handle anything here
return;
}
data.value = await resp.json();
} catch (error) {
window.adminforth.alert({
message: `Error fetching data: ${error.message}`,
variant: 'danger',
timeout: 'unlimited'
});
}
const apartsByDaysReverse = data.value.apartsByDays.reverse();
optionsC1.series[0].data = apartsByDaysReverse.map((item) => item.count);
optionsC1.xaxis.categories = apartsByDaysReverse.map((item) => dayjs(item.day).format('DD MMM'));
const chart = new ApexCharts(document.getElementById("area-chart"), optionsC1);
chart.render();
optionsC2.series[0].data = data.value.listedVsUnlistedByDays.map((item) => item.listed);
optionsC2.series[1].data = data.value.listedVsUnlistedByDays.map((item) => item.unlisted);
optionsC2.xaxis.categories = data.value.listedVsUnlistedByDays.map((item) => dayjs(item.day).format('DD MMM'));
const chart2 = new ApexCharts(document.getElementById("bar-chart"), optionsC2);
chart2.render();
optionsC3.series[0].data = data.value.listedVsUnlistedPriceByDays.map((item) => item.listedPrice.toFixed(2));
optionsC3.series[1].data = data.value.listedVsUnlistedPriceByDays.map((item) => item.unlistedPrice.toFixed(2));
optionsC3.xaxis.categories = data.value.listedVsUnlistedPriceByDays.map((item) => dayjs(item.day).format('DD MMM'));
const chart3 = new ApexCharts(document.getElementById("size-chart"), optionsC3);
chart3.render();
})
</script>
diff --git a/docs/tutorial/Customization/hooks/index.html b/docs/tutorial/Customization/hooks/index.html index 97aba2f04..ccf048a75 100644 --- a/docs/tutorial/Customization/hooks/index.html +++ b/docs/tutorial/Customization/hooks/index.html @@ -13,7 +13,7 @@ - + @@ -28,6 +28,6 @@🫨 use https://flowbite.com/ to copy-paste pre-designed tailwind design blocks for your pages
Let's add reference to adminUser
when user creates a new apartment:
import type { AdminUser } from 'adminforth/types/AdminForthConfig.js';
{
...
resourceId: 'aparts',
columns: [
...
{
name: 'realtor_id',
...
+ showIn: ['list', 'show', 'edit'], // don't even show this field in create
...
},
...
],
...
+ hooks: {
+ create: {
+ beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, record: any }) => {
+ if (adminUser.isRoot) {
+ return { ok: false, error: "Root user can't create appartment, relogin as DB user" };
+ }
+ record.realtor_id = adminUser.dbUser.id;
+ return { ok: true, record };
+ }
+ }
+ }
}
import type { AdminUser } from 'adminforth/types/AdminForthConfig.js';
{
...
resourceId: 'aparts',
columns: [
...
{
name: 'realtor_id',
...
showIn: ['list', 'show', 'edit'], // don't even show this field in create
...
},
...
],
...
hooks: {
create: {
beforeSave: async ({ adminUser, record }: { adminUser: AdminUser, record: any }) => {
if (adminUser.isRoot) {
return { ok: false, error: "Root user can't create appartment, relogin as DB user" };
}
record.realtor_id = adminUser.dbUser.id;
return { ok: true, record };
}
}
}
}