mirror of
https://github.com/frappe/books.git
synced 2025-03-16 16:12:22 +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 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;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
40
src/App.vue
40
src/App.vue
@ -1,46 +1,24 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<frappe-desk v-if="showDesk" :sidebarConfig="sidebarConfig">
|
||||
<router-view />
|
||||
</frappe-desk>
|
||||
<router-view v-else name="setup" />
|
||||
<div class="row no-gutters">
|
||||
<sidebar class="col-2" />
|
||||
<div class="page-container col-10 bg-light">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import Observable from 'frappejs/utils/observable';
|
||||
import Desk from 'frappejs/ui/components/Desk';
|
||||
import sidebarConfig from './sidebarConfig';
|
||||
import Sidebar from './components/Sidebar';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
showDesk: true,
|
||||
sidebarConfig
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FrappeDesk: Desk,
|
||||
},
|
||||
async beforeRouteUpdate(to, from, next) {
|
||||
const accountingSettings = await frappe.getSingle('AccountingSettings');
|
||||
if (accountingSettings.companyName) {
|
||||
this.showDesk = true;
|
||||
} else {
|
||||
this.showDesk = true;
|
||||
}
|
||||
Sidebar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import '~frappe-datatable/dist/frappe-datatable';
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@import "styles/index.scss";
|
||||
</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 Router from 'vue-router';
|
||||
import coreRoutes from 'frappejs/ui/routes';
|
||||
|
||||
import SetupWizard from '../pages/SetupWizard';
|
||||
import Report from 'frappejs/ui/pages/Report';
|
||||
import reportViewConfig from '../../reports/view';
|
||||
import ListView from '../pages/ListView';
|
||||
import FormView from '../pages/FormView';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
const routes = [].concat(coreRoutes, [
|
||||
const routes = [
|
||||
{
|
||||
path: '/setup-wizard',
|
||||
name: 'SetupWizard',
|
||||
components: {
|
||||
setup: SetupWizard
|
||||
}
|
||||
path: '/list/:doctype',
|
||||
name: 'ListView',
|
||||
component: ListView,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/report/:reportName',
|
||||
name: 'Report',
|
||||
component: Report,
|
||||
props: (route) => {
|
||||
const { reportName } = route.params;
|
||||
return {
|
||||
reportName,
|
||||
reportConfig: reportViewConfig[reportName] || null,
|
||||
filters: route.query
|
||||
};
|
||||
}
|
||||
path: '/edit/:doctype/:name',
|
||||
name: 'FormView',
|
||||
component: FormView,
|
||||
props: true
|
||||
}
|
||||
]);
|
||||
];
|
||||
|
||||
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/variables";
|
||||
@import "~bootstrap/scss/variables";
|
||||
|
||||
$dark: #252529;
|
||||
$blue: #1665D8;
|
Loading…
x
Reference in New Issue
Block a user