├── .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 |
![your image]()
28 |
34 |
35 |
36 |
55 |
56 |
63 |
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | {{item.cost * item.qty | currency: currencySymbol}}
78 |
79 |
80 |
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 |
104 |
105 |
106 |
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 |
--------------------------------------------------------------------------------