mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
Initialize new theme and pages from scratch
- Add ListView and FormView
This commit is contained in:
parent
91f6c693e0
commit
fa12e0f1d6
@ -151,5 +151,25 @@ module.exports = {
|
|||||||
<div class='col-4 text-muted'>${data.customer}</div>
|
<div class='col-4 text-muted'>${data.customer}</div>
|
||||||
<div class='col-4 text-muted text-right'>${frappe.format(data.grandTotal, 'Currency')}</div>`;
|
<div class='col-4 text-muted text-right'>${frappe.format(data.grandTotal, 'Currency')}</div>`;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
listView: {
|
||||||
|
columns: [
|
||||||
|
'customer',
|
||||||
|
{
|
||||||
|
label: 'Status',
|
||||||
|
getValue(doc) {
|
||||||
|
return doc.submitted ? 'Paid' : 'Pending';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'grandTotal',
|
||||||
|
'date',
|
||||||
|
{
|
||||||
|
label: 'INV #',
|
||||||
|
getValue(doc) {
|
||||||
|
return doc.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
38
src/App.vue
38
src/App.vue
@ -1,46 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<frappe-desk v-if="showDesk" :sidebarConfig="sidebarConfig">
|
<div class="row no-gutters">
|
||||||
|
<sidebar class="col-2" />
|
||||||
|
<div class="page-container col-10 bg-light">
|
||||||
<router-view />
|
<router-view />
|
||||||
</frappe-desk>
|
</div>
|
||||||
<router-view v-else name="setup" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import Sidebar from './components/Sidebar';
|
||||||
import Observable from 'frappejs/utils/observable';
|
|
||||||
import Desk from 'frappejs/ui/components/Desk';
|
|
||||||
import sidebarConfig from './sidebarConfig';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showDesk: true,
|
|
||||||
sidebarConfig
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
FrappeDesk: Desk,
|
Sidebar
|
||||||
},
|
|
||||||
async beforeRouteUpdate(to, from, next) {
|
|
||||||
const accountingSettings = await frappe.getSingle('AccountingSettings');
|
|
||||||
if (accountingSettings.companyName) {
|
|
||||||
this.showDesk = true;
|
|
||||||
} else {
|
|
||||||
this.showDesk = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "~bootstrap/scss/bootstrap";
|
@import "styles/index.scss";
|
||||||
@import '~frappe-datatable/dist/frappe-datatable';
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
10
src/components/PageHeader.vue
Normal file
10
src/components/PageHeader.vue
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-header px-4 py-3 border-bottom bg-white">
|
||||||
|
<h5 class="m-0">{{ title }}</h5>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['title']
|
||||||
|
}
|
||||||
|
</script>
|
3
src/components/SearchInput.vue
Normal file
3
src/components/SearchInput.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<input type="search" class="form-control" placeholder="Search..." autocomplete="off" spellcheck="false">
|
||||||
|
</template>
|
70
src/components/Sidebar.vue
Normal file
70
src/components/Sidebar.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-sidebar bg-dark p-2 text-light">
|
||||||
|
<div class="company-name px-3 py-2 my-2">
|
||||||
|
<h6 class="m-0">{{ companyName }}</h6>
|
||||||
|
</div>
|
||||||
|
<div :class="['sidebar-item px-3 py-2 ', isCurrentRoute(item.route) ? 'active' : '']" @click="routeTo(item.route)" v-for="item in items" :key="item.label">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
companyName: '',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Invoices',
|
||||||
|
route: '/list/Invoice'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Customers',
|
||||||
|
route: '/list/Party'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Items',
|
||||||
|
route: '/list/Item'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reports',
|
||||||
|
route: '/reports'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
route: '/settings'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const accountingSettings = await frappe.getDoc('AccountingSettings');
|
||||||
|
this.companyName = accountingSettings.companyName;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isCurrentRoute(route) {
|
||||||
|
return this.$route.path === route;
|
||||||
|
},
|
||||||
|
routeTo(route) {
|
||||||
|
this.$router.push(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../styles/variables.scss";
|
||||||
|
|
||||||
|
.page-sidebar {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-item {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
50
src/pages/FormView/index.vue
Normal file
50
src/pages/FormView/index.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-light">
|
||||||
|
<page-header :title="name" />
|
||||||
|
<div class="form-container col-8 bg-white mt-4 ml-auto mr-auto border px-4 py-3">
|
||||||
|
<form-layout
|
||||||
|
class="p-3"
|
||||||
|
v-if="shouldRenderForm"
|
||||||
|
:doc="doc"
|
||||||
|
:fields="meta.fields"
|
||||||
|
:layout="meta.layout"
|
||||||
|
:invalid="invalid"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import frappe from 'frappejs';
|
||||||
|
import FormLayout from 'frappejs/ui/components/Form/FormLayout';
|
||||||
|
import PageHeader from '@/components/PageHeader';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormView',
|
||||||
|
props: ['doctype', 'name'],
|
||||||
|
components: {
|
||||||
|
PageHeader,
|
||||||
|
FormLayout
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
doc: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
shouldRenderForm() {
|
||||||
|
return this.name && this.doc;
|
||||||
|
},
|
||||||
|
meta() {
|
||||||
|
return frappe.getMeta(this.doctype);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.form-container {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
67
src/pages/ListView/List.vue
Normal file
67
src/pages/ListView/List.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-container">
|
||||||
|
<list-row class="text-muted rounded-top bg-light">
|
||||||
|
<list-cell v-for="column in columns" :key="column.label">
|
||||||
|
{{ column.label }}
|
||||||
|
</list-cell>
|
||||||
|
</list-row>
|
||||||
|
<list-row v-for="doc in data" :key="doc.name" @click.native="openForm(doc.name)">
|
||||||
|
<list-cell v-for="column in columns" :key="column.label">
|
||||||
|
{{ column.getValue(doc) }}
|
||||||
|
</list-cell>
|
||||||
|
</list-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import ListRow from './ListRow';
|
||||||
|
import ListCell from './ListCell';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List',
|
||||||
|
props: ['doctype'],
|
||||||
|
components: {
|
||||||
|
ListRow,
|
||||||
|
ListCell
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
columns: [],
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.prepareColumns();
|
||||||
|
|
||||||
|
this.data = await frappe.db.getAll({
|
||||||
|
doctype: this.doctype,
|
||||||
|
fields: ['*']
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openForm(name) {
|
||||||
|
console.log(name);
|
||||||
|
this.$router.push(`/edit/${this.doctype}/${name}`);
|
||||||
|
},
|
||||||
|
prepareColumns() {
|
||||||
|
this.columns = this.meta.listView.columns.map(col => {
|
||||||
|
if (typeof col === 'string') {
|
||||||
|
const field = this.meta.getField(col);
|
||||||
|
if (!field) return null;
|
||||||
|
return {
|
||||||
|
label: field.label,
|
||||||
|
getValue(doc) {
|
||||||
|
return doc[col];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
}).filter(Boolean);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
meta() {
|
||||||
|
return frappe.getMeta(this.doctype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
10
src/pages/ListView/ListCell.vue
Normal file
10
src/pages/ListView/ListCell.vue
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<div class="col">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['col']
|
||||||
|
}
|
||||||
|
</script>
|
21
src/pages/ListView/ListRow.vue
Normal file
21
src/pages/ListView/ListRow.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-row row no-gutters py-2 px-3">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../styles/variables";
|
||||||
|
.list-row {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
background-color: var(--white);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row:not(:first-child) {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
</style>
|
28
src/pages/ListView/ListToolbar.vue
Normal file
28
src/pages/ListView/ListToolbar.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 d-flex">
|
||||||
|
<search-input class="mr-2" />
|
||||||
|
<f-button secondary>{{ _('Add Filter') }}</f-button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 d-flex flex-row-reverse">
|
||||||
|
<f-button primary @click="$emit('newClick')">{{ _('New {0}', doctype) }}</f-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import SearchInput from '@/components/SearchInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ListToolbar',
|
||||||
|
props: ['doctype'],
|
||||||
|
components: {
|
||||||
|
SearchInput
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async newInvoice() {
|
||||||
|
const doc = await frappe.getNewDoc('Invoice');
|
||||||
|
this.$formModal.open(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
42
src/pages/ListView/index.vue
Normal file
42
src/pages/ListView/index.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-light">
|
||||||
|
<page-header :title="meta.label || meta.name" />
|
||||||
|
<div class="px-4 py-3">
|
||||||
|
<list-toolbar
|
||||||
|
:doctype="doctype"
|
||||||
|
@newClick="openNewForm"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<list
|
||||||
|
:doctype="doctype"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import frappe from 'frappejs';
|
||||||
|
import PageHeader from '@/components/PageHeader';
|
||||||
|
import ListToolbar from './ListToolbar';
|
||||||
|
import List from './List';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ListView',
|
||||||
|
props: ['doctype'],
|
||||||
|
components: {
|
||||||
|
PageHeader,
|
||||||
|
ListToolbar,
|
||||||
|
List
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
meta() {
|
||||||
|
return frappe.getMeta(this.doctype);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async openNewForm() {
|
||||||
|
const doc = await frappe.getNewDoc(this.doctype);
|
||||||
|
this.$router.push(`/edit/${this.doctype}/${doc.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,34 +1,24 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import coreRoutes from 'frappejs/ui/routes';
|
|
||||||
|
|
||||||
import SetupWizard from '../pages/SetupWizard';
|
import ListView from '../pages/ListView';
|
||||||
import Report from 'frappejs/ui/pages/Report';
|
import FormView from '../pages/FormView';
|
||||||
import reportViewConfig from '../../reports/view';
|
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
const routes = [].concat(coreRoutes, [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/setup-wizard',
|
path: '/list/:doctype',
|
||||||
name: 'SetupWizard',
|
name: 'ListView',
|
||||||
components: {
|
component: ListView,
|
||||||
setup: SetupWizard
|
props: true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/report/:reportName',
|
path: '/edit/:doctype/:name',
|
||||||
name: 'Report',
|
name: 'FormView',
|
||||||
component: Report,
|
component: FormView,
|
||||||
props: (route) => {
|
props: true
|
||||||
const { reportName } = route.params;
|
|
||||||
return {
|
|
||||||
reportName,
|
|
||||||
reportConfig: reportViewConfig[reportName] || null,
|
|
||||||
filters: route.query
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
];
|
||||||
]);
|
|
||||||
|
|
||||||
export default new Router({ routes });
|
export default new Router({ routes });
|
||||||
|
6
src/styles/index.scss
Normal file
6
src/styles/index.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@import "variables.scss";
|
||||||
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
@ -1,2 +1,5 @@
|
|||||||
@import "~bootstrap/scss/functions";
|
@import "~bootstrap/scss/functions";
|
||||||
@import "~bootstrap/scss/variables";
|
@import "~bootstrap/scss/variables";
|
||||||
|
|
||||||
|
$dark: #252529;
|
||||||
|
$blue: #1665D8;
|
Loading…
Reference in New Issue
Block a user