├── .gitignore ├── README.md ├── css └── style.css ├── images └── metaware_logo.png ├── index.html └── js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Angular Invoicing 2 | 3 | Use this extremely small and lightweight project built in Angular JS to build your one off Invoices. No monthly fees, No Subscriptions - No heavy duty features, Just simple invoices. 4 | 5 | ## Usage 6 | 7 | Use this project here: [Angular Invoicing](http://metaware.github.io/angular-invoicing) or clone it and customize it to your hearts content. 8 | 9 | ## Feature Requests 10 | 11 | Feel free to open any issues/pull requests if you have any feature requests. 12 | 13 | ## Roadmap 14 | 15 | * Add Discounts feature 16 | * Add multiple currency support 17 | 18 | 19 | ## Contributors 20 | 21 | * [Manpreet Singh](http://github.com/manpreetrules) 22 | * [Jasdeep Singh](http://jasdeep.ca) 23 | * [William Yaworsky](https://github.com/yaworsw) 24 | 25 | ##Demo Link 26 | http://metaware.github.io/angular-invoicing/ 27 | 28 | 29 | ## License 30 | 31 | No license restrictions, use it however and wherever you want - Commercial, Personal, Non-profit however you please. Just send us a quick note letting us know this project came handy and we'll be flattered. :) 32 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | .slide-down-enter, 2 | .slide-down-leave 3 | { 4 | -webkit-transition:200ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 5 | -moz-transition:200ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 6 | -ms-transition:200ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 7 | -o-transition:200ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 8 | transition:200ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 9 | display:block; 10 | overflow:hidden; 11 | position:relative; 12 | } 13 | 14 | .items-table .row { 15 | border-bottom:1px solid #ddd; 16 | line-height:3em; 17 | } 18 | .items-table .row:last-child { 19 | border-bottom:none; 20 | line-height:3em; 21 | } 22 | 23 | .slide-down-enter.slide-down-enter-active, 24 | .slide-down-leave { 25 | opacity:1; 26 | height:46px; 27 | } 28 | 29 | .slide-down-leave.slide-down-leave-active, 30 | .slide-down-enter { 31 | opacity:0; 32 | height:0px; 33 | } 34 | 35 | 36 | .invoice-number-container * { 37 | font-weight:bold; 38 | } 39 | 40 | .items-table .row:nth-child(even) { 41 | background:#f9f9f9; 42 | } 43 | .items-table input { 44 | line-height:1.5em; 45 | } 46 | .actions { 47 | padding-top:1em; 48 | } 49 | input:focus { 50 | outline: 0; 51 | } 52 | 53 | .heading { 54 | background-color:#357EBD; 55 | color:#FFF; 56 | margin-bottom:1em; 57 | text-align:center; 58 | line-height:2.5em; 59 | } 60 | .branding { 61 | padding-bottom:2em; 62 | border-bottom:1px solid #ddd; 63 | } 64 | .logo-container { 65 | text-align:right; 66 | } 67 | .infos .right { 68 | text-align:right; 69 | } 70 | .infos .right input { 71 | text-align:right; 72 | } 73 | .infos .input-container { 74 | padding:3px 0; 75 | } 76 | 77 | .header.row { 78 | font-weight:bold; 79 | border-bottom:1px solid #ddd; 80 | border-top:1px solid #ddd; 81 | } 82 | 83 | input, textarea{ 84 | border: 1px solid #FFF; 85 | } 86 | 87 | .container input:hover, .container textarea:hover, 88 | .table-striped > tbody > tr:nth-child(2n+1) > td input:hover, 89 | .container input:focus, .container textarea:focus, 90 | .table-striped > tbody > tr:nth-child(2n+1) > td input:focus{ 91 | border: 1px solid #CCC; 92 | } 93 | 94 | .table-striped > tbody > tr:nth-child(2n+1) > td input{ 95 | background-color: #F9F9F9; 96 | border: 1px solid #F9F9F9; 97 | } 98 | 99 | 100 | 101 | @media print { 102 | .noPrint { 103 | display:none; 104 | } 105 | } 106 | 107 | body{ 108 | padding:20px; 109 | } 110 | 111 | .infos input{ 112 | width: 300px; 113 | } 114 | 115 | .align-right input{ 116 | text-align:right; 117 | width: 300px; 118 | } 119 | 120 | div.container{ 121 | width: 800px; 122 | } 123 | 124 | #imgInp{ 125 | display: none; 126 | } 127 | 128 | .copy { 129 | font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 130 | width: 100%; 131 | margin: 40px 0 20px 0; 132 | font-size: 10px; 133 | color: rgba(0, 0, 0, 0.5); 134 | text-align: center; 135 | color: #404040; 136 | cursor: default; 137 | line-height: 1.4em; 138 | } 139 | 140 | .copy .love { 141 | display: inline-block; 142 | position: relative; 143 | color: #ce0c15; 144 | } 145 | -------------------------------------------------------------------------------- /images/metaware_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaware/angular-invoicing/c0d4597194dfe70f12a1e0d383c14f3ce448a1fe/images/metaware_logo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Invoicing - Built with AngularJS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | INVOICE 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
 
58 |
Description
59 |
Quantity
60 |
Cost {{currencySymbol}}
61 |
Total
62 |
63 |
64 |
65 | [X] 66 |
67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 | {{item.cost * item.qty | currency: currencySymbol}} 78 |
79 |
80 |
81 |
82 | [+] 83 |
84 |
85 |
86 |
Sub Total
87 |
{{invoiceSubTotal() | currency: currencySymbol}}
88 |
89 |
90 |
Tax(%):
91 |
{{calculateTax() | currency: currencySymbol}}
92 |
93 |
94 |
Grand Total:
95 |
{{calculateGrandTotal() | currency: currencySymbol}}
96 |
97 |
98 |
99 | Print 100 | Reset 101 | Turn On Print Mode 102 | Turn Off Print Mode 103 |
104 |
105 | 106 |
107 | Jasdeep Singh & 108 | Manpreet Singh 109 | Made with 110 | in Toronto by 111 | Metaware Labs Inc. 112 |
113 | 114 | 115 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | angular.module('invoicing', []) 2 | 3 | // The default logo for the invoice 4 | .constant('DEFAULT_LOGO', 'images/metaware_logo.png') 5 | 6 | // The invoice displayed when the user first uses the app 7 | .constant('DEFAULT_INVOICE', { 8 | tax: 13.00, 9 | invoice_number: 10, 10 | customer_info: { 11 | name: 'Mr. John Doe', 12 | web_link: 'John Doe Designs Inc.', 13 | address1: '1 Infinite Loop', 14 | address2: 'Cupertino, California, US', 15 | postal: '90210' 16 | }, 17 | company_info: { 18 | name: 'Metaware Labs', 19 | web_link: 'www.metawarelabs.com', 20 | address1: '123 Yonge Street', 21 | address2: 'Toronto, ON, Canada', 22 | postal: 'M5S 1B6' 23 | }, 24 | items:[ 25 | { qty: 10, description: 'Gadget', cost: 9.95 } 26 | ] 27 | }) 28 | 29 | // Service for accessing local storage 30 | .service('LocalStorage', [function() { 31 | 32 | var Service = {}; 33 | 34 | // Returns true if there is a logo stored 35 | var hasLogo = function() { 36 | return !!localStorage['logo']; 37 | }; 38 | 39 | // Returns a stored logo (false if none is stored) 40 | Service.getLogo = function() { 41 | if (hasLogo()) { 42 | return localStorage['logo']; 43 | } else { 44 | return false; 45 | } 46 | }; 47 | 48 | Service.setLogo = function(logo) { 49 | localStorage['logo'] = logo; 50 | }; 51 | 52 | // Checks to see if an invoice is stored 53 | var hasInvoice = function() { 54 | return !(localStorage['invoice'] == '' || localStorage['invoice'] == null); 55 | }; 56 | 57 | // Returns a stored invoice (false if none is stored) 58 | Service.getInvoice = function() { 59 | if (hasInvoice()) { 60 | return JSON.parse(localStorage['invoice']); 61 | } else { 62 | return false; 63 | } 64 | }; 65 | 66 | Service.setInvoice = function(invoice) { 67 | localStorage['invoice'] = JSON.stringify(invoice); 68 | }; 69 | 70 | // Clears a stored logo 71 | Service.clearLogo = function() { 72 | localStorage['logo'] = ''; 73 | }; 74 | 75 | // Clears a stored invoice 76 | Service.clearinvoice = function() { 77 | localStorage['invoice'] = ''; 78 | }; 79 | 80 | // Clears all local storage 81 | Service.clear = function() { 82 | localStorage['invoice'] = ''; 83 | Service.clearLogo(); 84 | }; 85 | 86 | return Service; 87 | 88 | }]) 89 | 90 | .service('Currency', [function(){ 91 | 92 | var service = {}; 93 | 94 | service.all = function() { 95 | return [ 96 | { 97 | name: 'British Pound (£)', 98 | symbol: '£' 99 | }, 100 | { 101 | name: 'Canadian Dollar ($)', 102 | symbol: 'CAD $ ' 103 | }, 104 | { 105 | name: 'Euro (€)', 106 | symbol: '€' 107 | }, 108 | { 109 | name: 'Indian Rupee (₹)', 110 | symbol: '₹' 111 | }, 112 | { 113 | name: 'Norwegian krone (kr)', 114 | symbol: 'kr ' 115 | }, 116 | { 117 | name: 'US Dollar ($)', 118 | symbol: '$' 119 | } 120 | ] 121 | } 122 | 123 | return service; 124 | 125 | }]) 126 | 127 | // Main application controller 128 | .controller('InvoiceCtrl', ['$scope', '$http', 'DEFAULT_INVOICE', 'DEFAULT_LOGO', 'LocalStorage', 'Currency', 129 | function($scope, $http, DEFAULT_INVOICE, DEFAULT_LOGO, LocalStorage, Currency) { 130 | 131 | // Set defaults 132 | $scope.currencySymbol = '$'; 133 | $scope.logoRemoved = false; 134 | $scope.printMode = false; 135 | 136 | (function init() { 137 | // Attempt to load invoice from local storage 138 | !function() { 139 | var invoice = LocalStorage.getInvoice(); 140 | $scope.invoice = invoice ? invoice : DEFAULT_INVOICE; 141 | }(); 142 | 143 | // Set logo to the one from local storage or use default 144 | !function() { 145 | var logo = LocalStorage.getLogo(); 146 | $scope.logo = logo ? logo : DEFAULT_LOGO; 147 | }(); 148 | 149 | $scope.availableCurrencies = Currency.all(); 150 | 151 | })() 152 | // Adds an item to the invoice's items 153 | $scope.addItem = function() { 154 | $scope.invoice.items.push({ qty:0, cost:0, description:"" }); 155 | } 156 | 157 | // Toggle's the logo 158 | $scope.toggleLogo = function(element) { 159 | $scope.logoRemoved = !$scope.logoRemoved; 160 | LocalStorage.clearLogo(); 161 | }; 162 | 163 | // Triggers the logo chooser click event 164 | $scope.editLogo = function() { 165 | // angular.element('#imgInp').trigger('click'); 166 | document.getElementById('imgInp').click(); 167 | }; 168 | 169 | $scope.printInfo = function() { 170 | window.print(); 171 | }; 172 | 173 | // Remotes an item from the invoice 174 | $scope.removeItem = function(item) { 175 | $scope.invoice.items.splice($scope.invoice.items.indexOf(item), 1); 176 | }; 177 | 178 | // Calculates the sub total of the invoice 179 | $scope.invoiceSubTotal = function() { 180 | var total = 0.00; 181 | angular.forEach($scope.invoice.items, function(item, key){ 182 | total += (item.qty * item.cost); 183 | }); 184 | return total; 185 | }; 186 | 187 | // Calculates the tax of the invoice 188 | $scope.calculateTax = function() { 189 | return (($scope.invoice.tax * $scope.invoiceSubTotal())/100); 190 | }; 191 | 192 | // Calculates the grand total of the invoice 193 | $scope.calculateGrandTotal = function() { 194 | saveInvoice(); 195 | return $scope.calculateTax() + $scope.invoiceSubTotal(); 196 | }; 197 | 198 | // Clears the local storage 199 | $scope.clearLocalStorage = function() { 200 | var confirmClear = confirm('Are you sure you would like to clear the invoice?'); 201 | if(confirmClear) { 202 | LocalStorage.clear(); 203 | setInvoice(DEFAULT_INVOICE); 204 | } 205 | }; 206 | 207 | // Sets the current invoice to the given one 208 | var setInvoice = function(invoice) { 209 | $scope.invoice = invoice; 210 | saveInvoice(); 211 | }; 212 | 213 | // Reads a url 214 | var readUrl = function(input) { 215 | if (input.files && input.files[0]) { 216 | var reader = new FileReader(); 217 | reader.onload = function (e) { 218 | document.getElementById('company_logo').setAttribute('src', e.target.result); 219 | LocalStorage.setLogo(e.target.result); 220 | } 221 | reader.readAsDataURL(input.files[0]); 222 | } 223 | }; 224 | 225 | // Saves the invoice in local storage 226 | var saveInvoice = function() { 227 | LocalStorage.setInvoice($scope.invoice); 228 | }; 229 | 230 | // Runs on document.ready 231 | angular.element(document).ready(function () { 232 | // Set focus 233 | document.getElementById('invoice-number').focus(); 234 | 235 | // Changes the logo whenever the input changes 236 | document.getElementById('imgInp').onchange = function() { 237 | readUrl(this); 238 | }; 239 | }); 240 | 241 | }]) 242 | --------------------------------------------------------------------------------