first commit

This commit is contained in:
snehalathad@aissel.com 2024-05-06 11:28:05 +05:30
parent d7c7674e6a
commit 14b75269bb
82 changed files with 2667 additions and 0 deletions

5
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

View File

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

46
frontend/README.md Normal file
View File

@ -0,0 +1,46 @@
# Frappe UI Starter
This template should help get you started developing custom frontend for Frappe
apps with Vue 3 and the Frappe UI package.
![Auth](https://user-images.githubusercontent.com/34810212/236846289-ac31c292-81ea-4456-be65-95773a4049be.png)
![Home](https://user-images.githubusercontent.com/34810212/236846299-fd534e2b-1c06-4f01-a4f2-91a27547cd55.png)
This boilerplate sets up Vue 3, Vue Router, TailwindCSS, and Frappe UI out of
the box. It also has basic authentication frontend.
## Usage
This template is meant to be cloned inside an existing Frappe App. Assuming your
apps name is `todo`. Clone this template in the root folder of your app using `degit`.
```
cd apps/todo
npx degit NagariaHussain/doppio_frappeui_starter frontend
cd frontend
yarn
yarn dev
```
In a development environment, you need to put the below key-value pair in your `site_config.json` file:
```
"ignore_csrf": 1
```
This will prevent `CSRFToken` errors while using the vite dev server. In production environment, the `csrf_token` is attached to the `window` object in `index.html` for you.
The Vite dev server will start on the port `8080`. This can be changed from `vite.config.js`.
The development server is configured to proxy your frappe app (usually running on port `8000`). If you have a site named `todo.test`, open `http://todo.test:8080` in your browser. If you see a button named "Click to send 'ping' request", congratulations!
If you notice the browser URL is `/frontend`, this is the base URL where your frontend app will run in production.
To change this, open `src/router.js` and change the base URL passed to `createWebHistory`.
## Resources
- [Vue 3](https://v3.vuejs.org/guide/introduction.html)
- [Vue Router](https://next.router.vuejs.org/guide/)
- [Frappe UI](https://github.com/frappe/frappe-ui)
- [TailwindCSS](https://tailwindcss.com/docs/utility-first)
- [Vite](https://vitejs.dev/guide/)

19
frontend/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Frappe UI App</title>
</head>
<body>
<div id="app"></div>
<div id="modals"></div>
<div id="popovers"></div>
<script>
window.csrf_token = '{{ frappe.session.csrf_token }}'
</script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

24
frontend/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "frappe-ui-frontend",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build --base=/assets/playbook/frontend/ && yarn copy-html-entry",
"preview": "vite preview",
"copy-html-entry": "cp ../playbook/public/frontend/index.html ../playbook/www/frontend.html"
},
"dependencies": {
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.3",
"vue": "^3.2.25",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.0.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.15",
"vite": "^2.7.2"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
frontend/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

5
frontend/src/App.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div>
<router-view />
</div>
</template>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,152 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url('Inter-Thin.woff2?v=3.12') format('woff2'),
url('Inter-Thin.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url('Inter-ThinItalic.woff2?v=3.12') format('woff2'),
url('Inter-ThinItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url('Inter-ExtraLight.woff2?v=3.12') format('woff2'),
url('Inter-ExtraLight.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url('Inter-ExtraLightItalic.woff2?v=3.12') format('woff2'),
url('Inter-ExtraLightItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('Inter-Light.woff2?v=3.12') format('woff2'),
url('Inter-Light.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url('Inter-LightItalic.woff2?v=3.12') format('woff2'),
url('Inter-LightItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('Inter-Regular.woff2?v=3.12') format('woff2'),
url('Inter-Regular.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('Inter-Italic.woff2?v=3.12') format('woff2'),
url('Inter-Italic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('Inter-Medium.woff2?v=3.12') format('woff2'),
url('Inter-Medium.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url('Inter-MediumItalic.woff2?v=3.12') format('woff2'),
url('Inter-MediumItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('Inter-SemiBold.woff2?v=3.12') format('woff2'),
url('Inter-SemiBold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url('Inter-SemiBoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-SemiBoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('Inter-Bold.woff2?v=3.12') format('woff2'),
url('Inter-Bold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('Inter-BoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-BoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('Inter-ExtraBold.woff2?v=3.12') format('woff2'),
url('Inter-ExtraBold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url('Inter-ExtraBoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-ExtraBoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url('Inter-Black.woff2?v=3.12') format('woff2'),
url('Inter-Black.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url('Inter-BlackItalic.woff2?v=3.12') format('woff2'),
url('Inter-BlackItalic.woff?v=3.12') format('woff');
}

View File

@ -0,0 +1,42 @@
import router from '@/router'
import { computed, reactive } from 'vue'
import { createResource } from 'frappe-ui'
import { userResource } from './user'
export function sessionUser() {
const cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
let _sessionUser = cookies.get('user_id')
if (_sessionUser === 'Guest') {
_sessionUser = null
}
return _sessionUser
}
export const session = reactive({
login: createResource({
url: 'login',
makeParams({ email, password }) {
return {
usr: email,
pwd: password,
}
},
onSuccess(data) {
userResource.reload()
session.user = sessionUser()
session.login.reset()
router.replace(data.default_route || '/')
},
}),
logout: createResource({
url: 'logout',
onSuccess() {
userResource.reset()
session.user = sessionUser()
router.replace({ name: 'Login' })
},
}),
user: sessionUser(),
isLoggedIn: computed(() => !!session.user),
})

12
frontend/src/data/user.js Normal file
View File

@ -0,0 +1,12 @@
import router from '@/router'
import { createResource } from 'frappe-ui'
export const userResource = createResource({
url: 'frappe.auth.get_logged_user',
cache: 'User',
onError(error) {
if (error && error.exc_type === 'AuthenticationError') {
router.push({ name: 'LoginPage' })
}
},
})

2
frontend/src/index.css Normal file
View File

@ -0,0 +1,2 @@
@import './assets/Inter/inter.css';
@import 'frappe-ui/src/style.css';

27
frontend/src/main.js Normal file
View File

@ -0,0 +1,27 @@
import './index.css'
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import {
Button,
Card,
Input,
setConfig,
frappeRequest,
resourcesPlugin,
} from 'frappe-ui'
let app = createApp(App)
setConfig('resourceFetcher', frappeRequest)
app.use(router)
app.use(resourcesPlugin)
app.component('Button', Button)
app.component('Card', Card)
app.component('Input', Input)
app.mount('#app')

View File

@ -0,0 +1,55 @@
<template>
<div class="mr-4 ml-4 mt-10">
<h1>Playbooks</h1>
<card>
<div class="flex flex-row justify-between">
<div class="flex flex-col justify-between">
<h4>Playbook Name</h4>
<Input
type="text"
v-model="playbook.playbookName"
style="width: 300px"
></Input>
<h4>Playbook Owner</h4>
<Input
type="text"
v-model="playbook.playbookOwner"
style="width: 300px"
></Input>
</div>
<div class="flex flex-col">
<h4>Informed Members</h4>
<Input
type="text"
v-model="playbook.informedMember"
style="width: 300px"
></Input>
<h4>Playbook Access</h4>
<Input
type="dropdown"
v-model="playbook.playbookAccess"
style="width: 300px"
></Input>
</div>
</div>
</card>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Dialog } from 'frappe-ui'
import { createResource } from 'frappe-ui'
import { session } from '../data/session'
const playbook = createResource({
playbookName: '',
playbookOwner: '',
informedMember: '',
playbookAccess: '',
playbookDescription: '',
department: '',
})
const showDialog = ref(false)
</script>

View File

@ -0,0 +1,37 @@
<template>
<div class="m-3 flex flex-row items-center justify-center">
<Card title="Login to your FrappeUI App!" class="w-full max-w-md mt-4">
<form class="flex flex-col space-y-2 w-full" @submit.prevent="submit">
<Input
required
name="email"
type="text"
placeholder="johndoe@email.com"
label="User ID"
/>
<Input
required
name="password"
type="password"
placeholder="••••••"
label="Password"
/>
<Button :loading="session.login.loading" variant="solid"
>Login</Button
>
</form>
</Card>
</div>
</template>
<script lang="ts" setup>
import { session } from '../data/session'
function submit(e) {
let formData = new FormData(e.target)
session.login.submit({
email: formData.get('email'),
password: formData.get('password'),
})
}
</script>

40
frontend/src/router.js Normal file
View File

@ -0,0 +1,40 @@
import { createRouter, createWebHistory } from 'vue-router'
import { session } from './data/session'
import { userResource } from '@/data/user'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/pages/Home.vue'),
},
{
name: 'Login',
path: '/account/login',
component: () => import('@/pages/Login.vue'),
},
]
let router = createRouter({
history: createWebHistory('/frontend'),
routes,
})
router.beforeEach(async (to, from, next) => {
let isLoggedIn = session.isLoggedIn
try {
await userResource.promise
} catch (error) {
isLoggedIn = false
}
if (to.name === 'Login' && isLoggedIn) {
next({ name: 'Home' })
} else if (to.name !== 'Login' && !isLoggedIn) {
next({ name: 'Login' })
} else {
next()
}
})
export default router

View File

@ -0,0 +1,12 @@
module.exports = {
presets: [require('frappe-ui/src/utils/tailwind.config')],
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
'./node_modules/frappe-ui/src/components/**/*.{vue,js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}

22
frontend/vite.config.js Normal file
View File

@ -0,0 +1,22 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import frappeui from 'frappe-ui/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [frappeui(), vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
outDir: `../${path.basename(path.resolve('..'))}/public/frontend`,
emptyOutDir: true,
target: 'es2015',
},
optimizeDeps: {
include: ['frappe-ui > feather-icons', 'showdown', 'engine.io-client'],
},
})

1709
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "playbook",
"version": "1.0.0",
"description": "Playbook app",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"postinstall": "cd frontend && yarn install",
"dev": "cd frontend && yarn dev",
"build": "cd frontend && yarn build"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

@ -227,3 +227,5 @@ app_license = "mit"
# "Logging DocType Name": 30 # days to retain logs
# }
website_route_rules = [{'from_route': '/frontend/<path:app_path>', 'to_route': 'frontend'},]

View File

View File

@ -0,0 +1,33 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-03-11 19:09:41.379978",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"users"
],
"fields": [
{
"fieldname": "users",
"fieldtype": "Link",
"in_list_view": 1,
"label": "users",
"options": "members",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-13 11:46:53.301506",
"modified_by": "Administrator",
"module": "Playbook",
"name": "informedusers",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2024, snehalatha and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class informedusers(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2024, snehalatha and contributors
// For license information, please see license.txt
// frappe.ui.form.on("members", {
// refresh(frm) {
// },
// });

View File

@ -0,0 +1,62 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:employee_name",
"creation": "2024-03-11 18:21:48.198570",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"employee_details_section",
"employee_name",
"employee_id"
],
"fields": [
{
"fieldname": "employee_details_section",
"fieldtype": "Section Break",
"label": "Employee Details"
},
{
"fieldname": "employee_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Employee name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "employee_id",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Employee Id",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-03-11 18:24:08.999807",
"modified_by": "Administrator",
"module": "Playbook",
"name": "members",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1,
"track_seen": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2024, snehalatha and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class members(Document):
pass

View File

@ -0,0 +1,9 @@
# Copyright (c) 2024, snehalatha and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class Testmembers(FrappeTestCase):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2024, snehalatha and contributors
// For license information, please see license.txt
// frappe.ui.form.on("playbooks", {
// refresh(frm) {
// },
// });

View File

@ -0,0 +1,126 @@
{
"actions": [],
"allow_guest_to_view": 1,
"allow_rename": 1,
"autoname": "field:playbook_name",
"creation": "2024-03-01 15:53:25.422486",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"section_break_cmct",
"playbook_owner",
"informed_members",
"ispublished",
"column_break_ingm",
"playbook_name",
"access",
"route",
"amended_from",
"section_break_zfjq",
"playbook_description"
],
"fields": [
{
"fieldname": "section_break_cmct",
"fieldtype": "Section Break"
},
{
"fetch_from": ".employee_name",
"fieldname": "playbook_owner",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Playbook owner",
"options": "members",
"reqd": 1,
"unique": 1
},
{
"fetch_from": ".",
"fieldname": "informed_members",
"fieldtype": "Table MultiSelect",
"label": "Informed members",
"options": "informedusers",
"reqd": 1
},
{
"fieldname": "playbook_description",
"fieldtype": "HTML Editor",
"label": "Playbook Description"
},
{
"fieldname": "column_break_ingm",
"fieldtype": "Column Break"
},
{
"fieldname": "playbook_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Playbook name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "access",
"fieldtype": "Select",
"label": "Team with access",
"options": "Public\nPrivate"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 1,
"label": "Amended From",
"no_copy": 1,
"options": "playbooks",
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "ispublished",
"fieldtype": "Check",
"label": "isPublished"
},
{
"fieldname": "route",
"fieldtype": "Data",
"label": "route"
},
{
"fieldname": "section_break_zfjq",
"fieldtype": "Section Break"
}
],
"has_web_view": 1,
"index_web_pages_for_search": 1,
"is_published_field": "ispublished",
"links": [],
"modified": "2024-03-15 10:42:25.917745",
"modified_by": "Administrator",
"module": "Playbook",
"name": "playbooks",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"route": "playbook",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1,
"track_seen": 1,
"track_views": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2024, snehalatha and contributors
# For license information, please see license.txt
# import frappe
from frappe.website.website_generator import WebsiteGenerator
class playbooks(WebsiteGenerator):
pass

View File

@ -0,0 +1,17 @@
{% extends "templates/web.html" %}
{% block page_content %}
<div>
<h3>{{ title }}</h3>
<h4>by : {{ doc.playbook_owner }}</h4>
<h4>informed users</h4>
{% for users in doc.informed_members %}
<p>{{ users.users }}</p>
{% endfor %}
</div>
{% endblock %}
<!-- this is a sample default web page template -->

View File

@ -0,0 +1,5 @@
<div>
<a href="{{ doc.route }}">{{ doc.title or doc.name }}</a>
</div>
<!-- this is a sample default list template -->

View File

@ -0,0 +1,9 @@
# Copyright (c) 2024, snehalatha and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class Testplaybooks(FrappeTestCase):
pass

View File

View File

@ -0,0 +1,3 @@
frappe.ready(function() {
// bind events here
})

View File

@ -0,0 +1,115 @@
{
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 0,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
"anonymous": 1,
"apply_document_permissions": 0,
"button_label": "Submit",
"condition_json": "[]",
"creation": "2024-03-14 11:16:14.149153",
"doc_type": "playbooks",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"introduction_text": "<div class=\"ql-editor read-mode\"><p>Fill the details</p></div>",
"is_standard": 1,
"list_columns": [],
"login_required": 0,
"max_attachment_size": 0,
"modified": "2024-03-14 11:22:04.441886",
"modified_by": "Administrator",
"module": "Playbook",
"name": "playbook",
"owner": "Administrator",
"published": 1,
"route": "playbookfill",
"show_attachments": 0,
"show_list": 0,
"show_sidebar": 0,
"title": "playbook",
"web_form_fields": [
{
"allow_read_on_all_link_options": 0,
"fieldname": "section_break_cmct",
"fieldtype": "Section Break",
"hidden": 0,
"max_length": 0,
"max_value": 0,
"precision": "",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "playbook_owner",
"fieldtype": "Link",
"hidden": 0,
"label": "Playbook owner",
"mandatory_depends_on": "",
"max_length": 0,
"max_value": 0,
"options": "members",
"precision": "",
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "column_break_ingm",
"fieldtype": "Column Break",
"hidden": 0,
"label": "",
"max_length": 0,
"max_value": 0,
"precision": "",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "playbook_name",
"fieldtype": "Data",
"hidden": 0,
"label": "Playbook name",
"max_length": 0,
"max_value": 0,
"precision": "",
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "access",
"fieldtype": "Select",
"hidden": 0,
"label": "Team with access",
"max_length": 0,
"max_value": 0,
"options": "Public\nPrivate",
"precision": "",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "section_break_zfjq",
"fieldtype": "Section Break",
"hidden": 0,
"label": "",
"max_length": 0,
"max_value": 0,
"precision": "",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
}
]
}

View File

@ -0,0 +1,5 @@
import frappe
def get_context(context):
# do your magic here
pass

4
yarn.lock Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1