2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

feat: New Frappe Accounting Design

- Add tailwindcss
- Implement Sidebar
This commit is contained in:
Faris Ansari 2019-10-03 19:16:12 +05:30
parent 15b3b1c662
commit f040d397bf
19 changed files with 279 additions and 210 deletions

View File

@ -61,7 +61,7 @@
"cross-env": "^5.2.0",
"file-saver": "^2.0.2",
"frappe-charts": "^1.2.4",
"frappejs": "https://github.com/thefalconx33/frappejs#test",
"frappejs": "0.0.10",
"nodemailer": "^4.7.0",
"popper.js": "^1.14.4",
"vue-color": "^2.7.0",

View File

@ -27,6 +27,7 @@ function createWindow() {
mainWindow = new BrowserWindow({
width: 1024,
height: 768,
frame: false,
useContentSize: true,
webPreferences: {
webSecurity: false,

View File

@ -1,12 +1,13 @@
<template>
<div id="app">
<desk v-if="showDesk" />
<div id="app" class="h-screen flex flex-col">
<Desk class="flex-1" v-if="showDesk" />
<database-selector v-if="showDatabaseSelector" @file="connectToDBFile" />
<setup-wizard v-if="showSetupWizard" />
</div>
</template>
<script>
import './styles/index.css';
import frappe from 'frappejs';
import Desk from './pages/Desk';
import SetupWizard from './pages/SetupWizard/SetupWizard';
@ -52,6 +53,3 @@ export default {
}
};
</script>
<style lang="scss">
@import 'styles/index.scss';
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,12 @@
<template>
<svg viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
<path
d="M4 .8v6.4M7.2 4H.8"
stroke="#112B42"
fill="none"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>

View File

@ -0,0 +1,41 @@
<template>
<svg v-if="!active" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path
d="M7.7 1c.368 0 .667.299.667.667v8a.667.667 0 01-.667.666H2.367a.667.667 0 01-.667-.666v-8c0-.368.299-.667.667-.667H7.7zm8 0c.368 0 .667.299.667.667v4a.667.667 0 01-.667.666h-5.333a.667.667 0 01-.667-.666v-4c0-.368.299-.667.667-.667H15.7z"
fill="#A1ABB4"
/>
<path
d="M7.7 11.667c.368 0 .667.298.667.666v4A.667.667 0 017.7 17H2.367a.667.667 0 01-.667-.667v-4c0-.368.299-.666.667-.666H7.7zm8-4c.368 0 .667.298.667.666v8A.667.667 0 0115.7 17h-5.333a.667.667 0 01-.667-.667v-8c0-.368.299-.666.667-.666H15.7z"
fill="#415668"
/>
</g>
</svg>
<svg v-else viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient x1="50%" y1="-124.512%" x2="50%" y2="100%" id="a">
<stop stop-color="#4AC3F8" offset="0%" />
<stop stop-color="#2490EF" offset="100%" />
</linearGradient>
</defs>
<g fill-rule="nonzero" transform="translate(-13 -10)" fill="url(#a)">
<path
d="M19.7 10c.368 0 .667.299.667.667v8a.667.667 0 01-.667.666h-5.333a.667.667 0 01-.667-.666v-8c0-.368.299-.667.667-.667H19.7zm8 0c.368 0 .667.299.667.667v4a.667.667 0 01-.667.666h-5.333a.667.667 0 01-.667-.666v-4c0-.368.299-.667.667-.667H27.7z"
opacity=".6"
/>
<path
d="M19.7 20.667c.368 0 .667.298.667.666v4A.667.667 0 0119.7 26h-5.333a.667.667 0 01-.667-.667v-4c0-.368.299-.666.667-.666H19.7zm8-4c.368 0 .667.298.667.666v8A.667.667 0 0127.7 26h-5.333a.667.667 0 01-.667-.667v-8c0-.368.299-.666.667-.666H27.7z"
/>
</g>
</svg>
</template>
<script>
export default {
props: {
active: {
default: false
}
}
};
</script>

View File

@ -0,0 +1,14 @@
<template>
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path
d="M15.333.5H.667A.667.667 0 000 1.167v2.666c0 .368.299.667.667.667h14.666A.667.667 0 0016 3.833V1.167A.667.667 0 0015.333.5z"
fill="#A1ABB4"
/>
<path
d="M14.667 5.833H1.333V14.5c0 .368.299.667.667.667h12a.667.667 0 00.667-.667V5.833zm-4 5.334H5.333V8.5h5.334v2.667z"
fill="#415668"
/>
</g>
</svg>
</template>

View File

@ -0,0 +1,11 @@
<template>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path
d="M17 4.897V15.5a2 2 0 01-2 2H3a2 2 0 01-2-2V4.897h16zm-4.364 2.77c-.436 0-.727.289-.727.722 0 1.589-1.309 2.889-2.909 2.889-1.6 0-2.91-1.3-2.91-2.89 0-.432-.29-.721-.726-.721-.437 0-.728.289-.728.722 0 2.383 1.964 4.333 4.364 4.333s4.364-1.95 4.364-4.333c0-.433-.291-.722-.728-.722z"
fill="#415668"
/>
<path d="M1 3.5l1.492-1.87A3 3 0 014.838.5h8.33a3 3 0 012.35 1.134L17 3.5H1z" fill="#A1ABB4" />
</g>
</svg>
</template>

View File

@ -0,0 +1,14 @@
<template>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill="#112B42" fill-rule="nonzero">
<path
d="M17.086 3.5v4.769h-4.364a.727.727 0 00-.56.264l-.063.089L10.67 11 8.307 5.092a.727.727 0 00-1.24-.189l-.06.084-1.97 3.282H1.087V3.5a2.5 2.5 0 012.5-2.5h11a2.5 2.5 0 012.5 2.5z"
opacity=".397"
/>
<path
d="M1.086 14.5V9.731h4.363a.727.727 0 00.561-.264l.063-.089L7.5 7l2.365 5.91c.09.225.284.388.516.44l.101.015h.058a.727.727 0 00.561-.264l.063-.088 1.97-3.282h3.952V14.5a2.5 2.5 0 01-2.5 2.5h-11a2.5 2.5 0 01-2.5-2.5z"
opacity=".8"
/>
</g>
</svg>
</template>

View File

@ -0,0 +1,11 @@
<template>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path
d="M17 7.112V14a2 2 0 01-2 2H3a2 2 0 01-2-2V7.112h16zM13.5 12h-4a.5.5 0 100 1h4a.5.5 0 100-1z"
fill="#415668"
/>
<path d="M3 2h12a2 2 0 012 2v1.645H1V4a2 2 0 012-2z" fill="#A1ABB4" />
</g>
</svg>
</template>

View File

@ -0,0 +1,14 @@
<template>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g fill-rule="nonzero" fill="none">
<path
d="M5 8.5h8c2.182 0 4-1.818 4-4s-1.818-4-4-4H5c-2.182 0-4 1.818-4 4s1.818 4 4 4zm0-6.545c1.382 0 2.545 1.163 2.545 2.545S6.382 7.045 5 7.045 2.455 5.882 2.455 4.5 3.618 1.955 5 1.955z"
fill="#9DA7B0"
/>
<path
d="M13 9.5H5c-2.182 0-4 1.818-4 4s1.818 4 4 4h8c2.182 0 4-1.818 4-4s-1.818-4-4-4zm0 6.545c-1.382 0-2.545-1.163-2.545-2.545s1.163-2.545 2.545-2.545 2.545 1.163 2.545 2.545-1.163 2.545-2.545 2.545z"
fill="#3F5467"
/>
</g>
</svg>
</template>

View File

@ -1,142 +1,91 @@
<template>
<div class="page-sidebar bg-dark p-2 text-light d-flex flex-column justify-content-between">
<div
class="pt-6 pb-2 px-2 w-64 h-full block window-drag bg-gray-200 flex justify-between flex-col"
>
<div>
<div class="company-name px-3 py-2 my-2">
<h6 class="m-0" @click="$router.push('/')">{{ companyName }}</h6>
<WindowControls class="px-3" />
<div class="mt-6 px-3">
<h6 class="text-sm font-semibold" @click="$router.push('/')">{{ companyName }}</h6>
</div>
<div>
<transition-group name="slide-fade" mode="out-in">
<div v-for="group in groups" :key="group">
<div class="mt-5">
<div
class="mt-1 first:mt-0"
v-for="group in groups"
:key="group.title"
@click="onGroupClick(group)"
>
<div class="px-3 py-2 flex items-center rounded cursor-pointer hover:bg-white">
<component :is="group.icon" class="w-5 h-5" :active="isActiveGroup(group) && !group.items" />
<div
:class="['sidebar-item px-1 py-2', activeGroup === group ? 'active' : '']"
@click="toggleGroup(group)"
style="user-select: none;"
class="ml-2 text-sm text-gray-900"
:class="isActiveGroup(group) && !group.items && 'text-blue-500'"
>
<div class="d-flex align-items-center">
<feather-icon
class="mr-1"
:name="openGroup === group ? 'chevron-down' : 'chevron-right'"
/>
{{group }}
</div>
{{ group.title }}
</div>
<transition name="slide-fade" mode="out-in">
<div v-if="openGroup === group">
<div
v-for="item in groupItems"
style="user-select: none;"
:class="['sidebar-item pl-4 py-2 ', isCurrentRoute(item.route) ? 'active' : '']"
@click="routeTo(item.route)"
:key="item.label"
>
<div class="d-flex align-items-center">{{ item.label }}</div>
</div>
</div>
</transition>
</div>
</transition-group>
<div v-if="group.items && isActiveGroup(group)">
<div
v-for="item in group.items"
:key="item.label"
class="mt-1 first:mt-0 text-sm text-gray-800 py-1 pl-10 rounded cursor-pointer hover:bg-white"
:class="itemActiveClass(item)"
@click="routeTo(item.route)"
>{{ item.label }}</div>
</div>
</div>
</div>
</div>
<div
class="sidebar-item px-3 py-2 d-flex align-items-center"
v-if="dbFileName"
@click="goToDatabaseSelector"
>
<feather-icon class="mr-2" name="settings"></feather-icon>
<span>{{ dbFileName }}</span>
<div>
<button
class="block bg-white rounded w-full h-8 flex justify-center focus:outline-none focus:shadow-outline"
>
<AddIcon class="w-3 h-3" />
</button>
</div>
</div>
</template>
<script>
import sidebarConfig from '../sidebarConfig';
import WindowControls from './WindowControls';
import AddIcon from './Icons/Add';
import { _ } from 'frappejs/utils';
export default {
data() {
return {
companyName: '',
dbFileName: '',
groups: [],
groupItems: [],
activeGroup: undefined,
openGroup: undefined
activeGroup: null
};
},
components: {
WindowControls,
AddIcon
},
async mounted() {
this.companyName = await sidebarConfig.getTitle();
this.dbFileName = await sidebarConfig.getDbName();
this.groups = sidebarConfig.getGroups();
this.setActive();
this.groups = sidebarConfig.groups;
this.activeGroup = this.groups.find(g => g.title === _('Sales'));
},
methods: {
setActive() {
itemActiveClass(item) {
let currentRoute = this.$route.fullPath;
// check each group items
this.groups.forEach(groupTitle => {
// filter items which contains the current route
sidebarConfig.getItems(groupTitle).some(item => {
// check for substring 'list' 'SalesInvoice' etc.
let found = false;
currentRoute.split('/').some(str => {
if (str.length) {
item.route.indexOf(str) > -1 ? (found = true) : (found = false);
}
if (found) {
this.toggleGroup(groupTitle);
return found;
}
});
return found;
});
});
return currentRoute === item.route ? 'bg-white text-blue-500' : '';
},
isCurrentRoute(route) {
if (this.activeGroup) return false;
return this.$route.fullPath === route;
isActiveGroup(group) {
return this.activeGroup && group.title === this.activeGroup.title;
},
toggleGroup(groupTitle) {
this.groupItems =
this.activeGroup === groupTitle
? []
: sidebarConfig.getItems(groupTitle);
this.activeGroup =
this.activeGroup === groupTitle ? undefined : groupTitle;
this.openGroup = this.activeGroup === groupTitle ? groupTitle : undefined;
onGroupClick(group) {
if (group.route) {
this.routeTo(group.route);
}
this.activeGroup = group;
},
routeTo(route) {
this.activeGroup = undefined;
this.activeGroup = null;
this.$router.push(route);
},
goToDatabaseSelector() {
localStorage.dbPath = '';
window.location.reload();
}
}
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
@import '../styles/animation.scss';
.company-name {
cursor: pointer;
}
.page-sidebar {
height: 100vh;
min-width: 208px;
max-width: 208px;
}
.sidebar-item {
cursor: pointer;
border-radius: $border-radius;
&:hover {
color: $gray-300;
}
&.active {
color: $white;
background-color: $frappe;
}
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<div class="flex">
<div @click="close" class="w-3 h-3 rounded-full bg-red-500 hover:bg-red-700"></div>
<div @click="minimize" class="ml-2 w-3 h-3 rounded-full bg-yellow-500 hover:bg-yellow-700"></div>
<div @click="maximize" class="ml-2 w-3 h-3 rounded-full bg-green-500 hover:bg-green-700"></div>
</div>
</template>
<script>
import electron from 'electron'
export default {
name: 'WindowControls',
methods: {
close() {
let window = electron.remote.getCurrentWindow();
window.close();
},
minimize() {
let window = electron.remote.getCurrentWindow();
window.minimize();
},
maximize() {
let window = electron.remote.getCurrentWindow();
window.maximize();
},
}
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="row no-gutters d-flex">
<sidebar class="sidebar" />
<div class="page-container bg-white">
<div class="flex">
<Sidebar />
<div class="bg-white">
<router-view :key="$route.fullPath" />
</div>
</div>

View File

@ -91,4 +91,7 @@ const routes = [
}
];
export default new Router({ routes });
let router = new Router({ routes });
router.replace('/list/SalesInvoice');
export default router;

View File

@ -1,97 +1,87 @@
import frappe from 'frappejs';
import { _ } from 'frappejs/utils';
const path = require('path');
import DashboardIcon from './components/Icons/Dashboard';
import SalesIcon from './components/Icons/Sales';
import PurchasesIcon from './components/Icons/Purchases';
import ReportsIcon from './components/Icons/Reports';
import SettingsIcon from './components/Icons/Settings';
const config = {
getTitle: async () => {
const { companyName, country } = await frappe.getSingle(
'AccountingSettings'
);
if (country === 'India') {
config.groups[2].items.push(
{
label: _('GSTR 1'),
route: '/report/gstr-1?transferType=B2B'
},
{
label: _('GSTR 2'),
route: '/report/gstr-2?transferType=B2B'
},
{
label: _('GSTR 3B'),
route: '/list/GSTR3B'
}
);
}
// if (country === 'India') {
// config.groups[2].items.push(
// {
// label: _('GSTR 1'),
// route: '/report/gstr-1?transferType=B2B'
// },
// {
// label: _('GSTR 2'),
// route: '/report/gstr-2?transferType=B2B'
// },
// {
// label: _('GSTR 3B'),
// route: '/list/GSTR3B'
// }
// );
// }
return companyName;
},
getDbName() {
if (localStorage.dbPath) {
const parts = localStorage.dbPath.split(path.sep);
return parts[parts.length - 1];
}
},
getGroups() {
return this.groups.map(g => g.title);
},
getItems(groupTitle) {
if (groupTitle)
return this.groups.filter(g => g.title === groupTitle)[0].items;
else return [];
},
groups: [
{
title: _('Masters'),
title: _('Dashboard'),
route: '/',
icon: DashboardIcon
},
{
title: _('Sales'),
icon: SalesIcon,
items: [
{
label: _('Chart Of Accounts'),
route: '/chartOfAccounts'
label: _('Invoice'),
route: '/list/SalesInvoice'
},
{
label: _('Accounts'),
route: '/list/Account'
},
{
label: _('Item'),
route: '/list/Item'
},
{
label: _('Customer'),
label: _('Customers'),
route: '/list/Customer'
},
{
label: _('Supplier'),
route: '/list/Supplier'
label: _('Items'),
route: '/list/Item'
},
{
label: _('Tax'),
label: _('Taxes'),
route: '/list/Tax'
}
]
},
{
title: _('Transactions'),
title: _('Purchases'),
icon: PurchasesIcon,
items: [
{
label: _('Sales Invoice'),
route: '/list/SalesInvoice'
},
{
label: _('Purchase Invoice'),
label: _('Bill'),
route: '/list/PurchaseInvoice'
},
{
label: _('Journal Entry'),
route: '/list/JournalEntry'
label: _('Suppliers'),
route: '/list/Supplier'
},
{
label: _('Payment'),
route: '/list/Payment'
label: _('Items'),
route: '/list/Item'
},
{
label: _('Taxes'),
route: '/list/Tax'
}
]
},
{
title: _('Reports'),
icon: ReportsIcon,
items: [
{
label: _('General Ledger'),
@ -108,33 +98,12 @@ const config = {
{
label: _('Trial Balance'),
route: '/report/trial-balance'
},
{
label: _('Sales Register'),
route: '/report/sales-register'
},
{
label: _('Purchase Register'),
route: '/report/purchase-register'
},
{
label: _('Bank Reconciliation'),
route: '/report/bank-reconciliation'
}
]
},
{
title: _('Tools'),
items: [
{
label: _('Data Import'),
route: '/data-import'
},
{
label: _('Settings'),
route: '/settings'
}
]
title: _('Settings'),
icon: SettingsIcon
}
]
};

View File

@ -1,15 +0,0 @@
.slide-fade-enter-active {
transition: all 0.4s ease 0.1s;
}
.slide-fade-leave-active {
transition: all 0.2s ease;
position: absolute;
}
.slide-fade-enter,
.slide-fade-leave-to {
transform: translateX(-5px);
opacity: 0;
}
.slide-fade-move {
transition: transform 0.3s 0.1s;
}

9
src/styles/index.css Normal file
View File

@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.window-drag {
-webkit-app-region: drag;
}

9
tailwind.config.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
theme: {
extend: { }
},
variants: {
margin: ['responsive', 'first', 'hover', 'focus'],
},
plugins: []
}