E-commerce App added.

This commit is contained in:
mustafahlvc 2017-11-01 14:32:25 +03:00
parent 93c2eab584
commit 47c2cc721e
32 changed files with 7194 additions and 913 deletions

918
package-lock.json generated
View File

@ -1299,7 +1299,6 @@
"requires": { "requires": {
"anymatch": "1.3.0", "anymatch": "1.3.0",
"async-each": "1.0.1", "async-each": "1.0.1",
"fsevents": "1.1.2",
"glob-parent": "2.0.0", "glob-parent": "2.0.0",
"inherits": "2.0.3", "inherits": "2.0.3",
"is-binary-path": "1.0.1", "is-binary-path": "1.0.1",
@ -3443,905 +3442,6 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true "dev": true
}, },
"fsevents": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
"integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
"dev": true,
"optional": true,
"requires": {
"nan": "2.6.2",
"node-pre-gyp": "0.6.36"
},
"dependencies": {
"abbrev": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
},
"ajv": {
"version": "4.11.8",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"co": "4.6.0",
"json-stable-stringify": "1.0.1"
}
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
},
"aproba": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.2.9"
}
},
"asn1": {
"version": "0.2.3",
"bundled": true,
"dev": true,
"optional": true
},
"assert-plus": {
"version": "0.2.0",
"bundled": true,
"dev": true,
"optional": true
},
"asynckit": {
"version": "0.4.0",
"bundled": true,
"dev": true,
"optional": true
},
"aws-sign2": {
"version": "0.6.0",
"bundled": true,
"dev": true,
"optional": true
},
"aws4": {
"version": "1.6.0",
"bundled": true,
"dev": true,
"optional": true
},
"balanced-match": {
"version": "0.4.2",
"bundled": true,
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
}
},
"block-stream": {
"version": "0.0.9",
"bundled": true,
"dev": true,
"requires": {
"inherits": "2.0.3"
}
},
"boom": {
"version": "2.10.1",
"bundled": true,
"dev": true,
"requires": {
"hoek": "2.16.3"
}
},
"brace-expansion": {
"version": "1.1.7",
"bundled": true,
"dev": true,
"requires": {
"balanced-match": "0.4.2",
"concat-map": "0.0.1"
}
},
"buffer-shims": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"caseless": {
"version": "0.12.0",
"bundled": true,
"dev": true,
"optional": true
},
"co": {
"version": "4.6.0",
"bundled": true,
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
},
"combined-stream": {
"version": "1.0.5",
"bundled": true,
"dev": true,
"requires": {
"delayed-stream": "1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"dev": true
},
"cryptiles": {
"version": "2.0.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"boom": "2.10.1"
}
},
"dashdash": {
"version": "1.14.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0"
},
"dependencies": {
"assert-plus": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"debug": {
"version": "2.6.8",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ms": "2.0.0"
}
},
"deep-extend": {
"version": "0.4.2",
"bundled": true,
"dev": true,
"optional": true
},
"delayed-stream": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"ecc-jsbn": {
"version": "0.1.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"jsbn": "0.1.1"
}
},
"extend": {
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"extsprintf": {
"version": "1.0.2",
"bundled": true,
"dev": true
},
"forever-agent": {
"version": "0.6.1",
"bundled": true,
"dev": true,
"optional": true
},
"form-data": {
"version": "2.1.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
"mime-types": "2.1.15"
}
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"fstream": {
"version": "1.0.11",
"bundled": true,
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"inherits": "2.0.3",
"mkdirp": "0.5.1",
"rimraf": "2.6.1"
}
},
"fstream-ignore": {
"version": "1.0.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"fstream": "1.0.11",
"inherits": "2.0.3",
"minimatch": "3.0.4"
}
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"aproba": "1.1.1",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.2"
}
},
"getpass": {
"version": "0.1.7",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0"
},
"dependencies": {
"assert-plus": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"glob": {
"version": "7.1.2",
"bundled": true,
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"graceful-fs": {
"version": "4.1.11",
"bundled": true,
"dev": true
},
"har-schema": {
"version": "1.0.5",
"bundled": true,
"dev": true,
"optional": true
},
"har-validator": {
"version": "4.2.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ajv": "4.11.8",
"har-schema": "1.0.5"
}
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"hawk": {
"version": "3.1.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9"
}
},
"hoek": {
"version": "2.16.3",
"bundled": true,
"dev": true
},
"http-signature": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"assert-plus": "0.2.0",
"jsprim": "1.4.0",
"sshpk": "1.13.0"
}
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
},
"ini": {
"version": "1.3.4",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"is-typedarray": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"dev": true
},
"isstream": {
"version": "0.1.2",
"bundled": true,
"dev": true,
"optional": true
},
"jodid25519": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"jsbn": "0.1.1"
}
},
"jsbn": {
"version": "0.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"json-schema": {
"version": "0.2.3",
"bundled": true,
"dev": true,
"optional": true
},
"json-stable-stringify": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"jsonify": "0.0.0"
}
},
"json-stringify-safe": {
"version": "5.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"jsonify": {
"version": "0.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"jsprim": {
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.0.2",
"json-schema": "0.2.3",
"verror": "1.3.6"
},
"dependencies": {
"assert-plus": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"mime-db": {
"version": "1.27.0",
"bundled": true,
"dev": true
},
"mime-types": {
"version": "2.1.15",
"bundled": true,
"dev": true,
"requires": {
"mime-db": "1.27.0"
}
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"dev": true,
"requires": {
"brace-expansion": "1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"node-pre-gyp": {
"version": "0.6.36",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"mkdirp": "0.5.1",
"nopt": "4.0.1",
"npmlog": "4.1.0",
"rc": "1.2.1",
"request": "2.81.0",
"rimraf": "2.6.1",
"semver": "5.3.0",
"tar": "2.2.1",
"tar-pack": "3.4.0"
}
},
"nopt": {
"version": "4.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"abbrev": "1.1.0",
"osenv": "0.1.4"
}
},
"npmlog": {
"version": "4.1.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
},
"oauth-sign": {
"version": "0.8.2",
"bundled": true,
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
"bundled": true,
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"os-homedir": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"dev": true
},
"performance-now": {
"version": "0.2.0",
"bundled": true,
"dev": true,
"optional": true
},
"process-nextick-args": {
"version": "1.0.7",
"bundled": true,
"dev": true
},
"punycode": {
"version": "1.4.1",
"bundled": true,
"dev": true,
"optional": true
},
"qs": {
"version": "6.4.0",
"bundled": true,
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"deep-extend": "0.4.2",
"ini": "1.3.4",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"readable-stream": {
"version": "2.2.9",
"bundled": true,
"dev": true,
"requires": {
"buffer-shims": "1.0.0",
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"string_decoder": "1.0.1",
"util-deprecate": "1.0.2"
}
},
"request": {
"version": "2.81.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"aws-sign2": "0.6.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.5",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.1.4",
"har-validator": "4.2.1",
"hawk": "3.1.3",
"http-signature": "1.1.1",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.15",
"oauth-sign": "0.8.2",
"performance-now": "0.2.0",
"qs": "6.4.0",
"safe-buffer": "5.0.1",
"stringstream": "0.0.5",
"tough-cookie": "2.3.2",
"tunnel-agent": "0.6.0",
"uuid": "3.0.1"
}
},
"rimraf": {
"version": "2.6.1",
"bundled": true,
"dev": true,
"requires": {
"glob": "7.1.2"
}
},
"safe-buffer": {
"version": "5.0.1",
"bundled": true,
"dev": true
},
"semver": {
"version": "5.3.0",
"bundled": true,
"dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"sntp": {
"version": "1.0.9",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"hoek": "2.16.3"
}
},
"sshpk": {
"version": "1.13.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jodid25519": "1.0.2",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
},
"dependencies": {
"assert-plus": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"string-width": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"stringstream": {
"version": "0.0.5",
"bundled": true,
"dev": true,
"optional": true
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"tar": {
"version": "2.2.1",
"bundled": true,
"dev": true,
"requires": {
"block-stream": "0.0.9",
"fstream": "1.0.11",
"inherits": "2.0.3"
}
},
"tar-pack": {
"version": "3.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"debug": "2.6.8",
"fstream": "1.0.11",
"fstream-ignore": "1.0.5",
"once": "1.4.0",
"readable-stream": "2.2.9",
"rimraf": "2.6.1",
"tar": "2.2.1",
"uid-number": "0.0.6"
}
},
"tough-cookie": {
"version": "2.3.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"punycode": "1.4.1"
}
},
"tunnel-agent": {
"version": "0.6.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"bundled": true,
"dev": true,
"optional": true
},
"uid-number": {
"version": "0.0.6",
"bundled": true,
"dev": true,
"optional": true
},
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"dev": true
},
"uuid": {
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"verror": {
"version": "1.3.6",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"extsprintf": "1.0.2"
}
},
"wide-align": {
"version": "1.1.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"string-width": "1.0.2"
}
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
}
}
},
"fstream": { "fstream": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
@ -8940,6 +8040,15 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true "dev": true
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-width": { "string-width": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -8967,15 +8076,6 @@
} }
} }
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"stringstream": { "stringstream": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",

View File

@ -36,6 +36,10 @@ const appRoutes: Routes = [
path : 'apps/calendar', path : 'apps/calendar',
loadChildren: './main/content/apps/calendar/calendar.module#FuseCalendarModule' loadChildren: './main/content/apps/calendar/calendar.module#FuseCalendarModule'
}, },
{
path : 'apps/e-commerce',
loadChildren: './main/content/apps/e-commerce/e-commerce.module#FuseEcommerceModule'
},
{ {
path : 'apps/todo', path : 'apps/todo',
loadChildren: './main/content/apps/todo/todo.module#FuseTodoModule' loadChildren: './main/content/apps/todo/todo.module#FuseTodoModule'

View File

@ -82,10 +82,12 @@ export class FuseUtils
{ {
function S4() function S4()
{ {
return (((1 + Math.random()) * 0x10000) || 0).toString(16).substring(1); return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
} }
return (S4() + S4()); return S4() + S4();
} }
public static toggleInArray(item, array) public static toggleInArray(item, array)
@ -99,4 +101,14 @@ export class FuseUtils
array.splice(array.indexOf(item), 1); array.splice(array.indexOf(item), 1);
} }
} }
public static handleize(text)
{
return text.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
} }

View File

@ -187,6 +187,11 @@ $matColorHues: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, A400
// Generate material element colors // Generate material element colors
// based on current contrast color // based on current contrast color
@include generateMaterialElementColors($contrastColor); @include generateMaterialElementColors($contrastColor);
&[disabled] {
background-color: rgba($color, .12) !important;
color: rgba($contrastColor, .26) !important;
}
} }
.#{$colorName}#{$hue}-fg { .#{$colorName}#{$hue}-fg {

View File

@ -96,7 +96,7 @@ $top-bg-image: url('assets/images/backgrounds/header-bg.png');
max-height: $carded-toolbar-height; max-height: $carded-toolbar-height;
} }
.content { > .content {
display: flex; display: flex;
flex: 1; flex: 1;
overflow: auto; overflow: auto;

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ import { ProjectsDashboardDb } from './projects-dashboard';
import { ScrumboardFakeDb } from './scrumboard'; import { ScrumboardFakeDb } from './scrumboard';
import { FaqFakeDb } from './faq'; import { FaqFakeDb } from './faq';
import { KnowledgeBaseFakeDb } from './knowledge-base'; import { KnowledgeBaseFakeDb } from './knowledge-base';
import { ECommerceFakeDb } from './e-commerce';
export class FuseFakeDbService implements InMemoryDbService export class FuseFakeDbService implements InMemoryDbService
{ {
@ -48,7 +49,10 @@ export class FuseFakeDbService implements InMemoryDbService
'projects-dashboard-widgets' : ProjectsDashboardDb.widgets, 'projects-dashboard-widgets' : ProjectsDashboardDb.widgets,
'scrumboard-boards' : ScrumboardFakeDb.boards, 'scrumboard-boards' : ScrumboardFakeDb.boards,
'faq' : FaqFakeDb.data, 'faq' : FaqFakeDb.data,
'knowledge-base' : KnowledgeBaseFakeDb.data 'knowledge-base' : KnowledgeBaseFakeDb.data,
'e-commerce-dashboard' : ECommerceFakeDb.dashboard,
'e-commerce-products' : ECommerceFakeDb.products,
'e-commerce-orders' : ECommerceFakeDb.orders
}; };
} }
} }

View File

@ -0,0 +1,325 @@
<div id="e-commerce-dashboard" class="page-layout simple fullwidth">
<!-- CONTENT -->
<div class="content p-24 w-100-p">
<!-- WIDGET GROUP -->
<div class="widget-group" fxLayout="row" fxFlex="100" fxLayoutWrap fxLayoutAlign="start start" *fuseIfOnDom [@animateStagger]="{value:'50'}">
<!-- WIDGET 1 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field>
<mat-select class="simplified font-size-16" [(ngModel)]="widgets.widget1.currentRange"
aria-label="Change range">
<mat-option *ngFor="let range of widgets.widget1.ranges | keys"
[value]="range.key">
{{range.value}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-icon-button fuseWidgetToggle aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<div class="pt-8 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div class="light-blue-fg font-size-72 line-height-72">
{{widgets.widget1.data.count[widgets.widget1.currentRange]}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget1.data.label}}</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget1.data.extra.label}}:</span>
<span class="h4 ml-8">{{widgets.widget1.data.extra.count[widgets.widget1.currentRange]}}</span>
</div>
</div>
<!-- / Front -->
<!-- Back -->
<div class="fuse-widget-back p-16 pt-32 mat-white-bg mat-elevation-z2">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="Flip widget">
<mat-icon class="s-16">close</mat-icon>
</button>
<div>
{{widgets.widget1.detail}}
</div>
</div>
<!-- / Back -->
</fuse-widget>
<!-- / WIDGET 1 -->
<!-- WIDGET 2 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget2.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<div class="pt-8 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div class="red-fg font-size-72 line-height-72">
{{widgets.widget2.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget2.data.label}}</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget2.data.extra.label}}:</span>
<span class="h4 ml-8">{{widgets.widget2.data.extra.count}}</span>
</div>
</div>
<!-- / Front -->
<!-- Back -->
<div class="fuse-widget-back p-16 pt-32 mat-white-bg mat-elevation-z2">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="Flip widget">
<mat-icon class="s-16">close</mat-icon>
</button>
<div>
{{widgets.widget2.detail}}
</div>
</div>
<!-- / Back -->
</fuse-widget>
<!-- / WIDGET 2 -->
<!-- WIDGET 3 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget3.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<div class="pt-8 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div class="orange-fg font-size-72 line-height-72">
{{widgets.widget3.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget3.data.label}}</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget3.data.extra.label}}:</span>
<span class="h4 ml-8">{{widgets.widget3.data.extra.count}}</span>
</div>
</div>
<!-- / Front -->
<!-- Back -->
<div class="fuse-widget-back p-16 pt-32 mat-white-bg mat-elevation-z2">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="Flip widget">
<mat-icon class="s-16">close</mat-icon>
</button>
<div>
{{widgets.widget3.detail}}
</div>
</div>
<!-- / Back -->
</fuse-widget>
<!-- / WIDGET 3 -->
<!-- WIDGET 4 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-xs="50" fxFlex.gt-md="25">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="pl-16 pr-8 py-16 h-52" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget4.title}}</div>
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button" aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
<div class="pt-8 pb-32" fxLayout="column" fxLayoutAlign="center center">
<div class="blue-grey-fg font-size-72 line-height-72">{{widgets.widget4.data.count}}
</div>
<div class="h3 secondary-text font-weight-500">{{widgets.widget4.data.label}}</div>
</div>
<div class="p-16 grey-50-bg border-top" fxLayout="row" fxLayoutAlign="start center">
<span class="h4 secondary-text text-truncate">{{widgets.widget4.data.extra.label}}:</span>
<span class="h4 ml-8">{{widgets.widget4.data.extra.count}}</span>
</div>
</div>
<!-- / Front -->
<!-- Back -->
<div class="fuse-widget-back p-16 pt-32 mat-white-bg mat-elevation-z2">
<button mat-icon-button fuseWidgetToggle class="fuse-widget-flip-button"
aria-label="Flip widget">
<mat-icon class="s-16">close</mat-icon>
</button>
<div>
{{widgets.widget4.detail}}
</div>
</div>
<!-- / Back -->
</fuse-widget>
<!-- / WIDGET 4 -->
<!-- WIDGET 5 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="row" fxFlex="100">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutWrap>
<div fxFlex class="py-8 h3">{{widgets.widget5.title}}</div>
<div fxFlex="0 1 auto" class="py-8" fxLayout="row">
<button mat-button class="px-16"
*ngFor="let range of widgets.widget5.ranges | keys"
(click)="widget5.currentRange = range.key"
[disabled]="widget5.currentRange == range.key">
{{range.value}}
</button>
</div>
</div>
<div class="h-420">
<ngx-charts-bar-vertical-stacked
*fuseIfOnDom
[scheme]="widget5.scheme"
[results]="this.widgets.widget5.mainChart[this.widget5.currentRange]"
[gradient]="widget5.gradient"
[xAxis]="widget5.xAxis"
[yAxis]="widget5.yAxis"
[legend]="widget5.legend"
[showXAxisLabel]="widget5.showXAxisLabel"
[showYAxisLabel]="widget5.showYAxisLabel"
[xAxisLabel]="widget5.xAxisLabel"
[yAxisLabel]="widget5.yAxisLabel"
(select)="widget5.onSelect($event)">
</ngx-charts-bar-vertical-stacked>
</div>
</div>
<!-- / Front -->
</fuse-widget>
<!-- / WIDGET 5 -->
<!-- WIDGET 6 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget6.title}}</div>
<mat-form-field>
<mat-select class="simplified" [(ngModel)]="widget6.currentRange" aria-label="Change range">
<mat-option *ngFor="let range of widgets.widget6.ranges | keys"
[value]="range.key">
{{range.value}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="h-400">
<ngx-charts-pie-chart
*fuseIfOnDom
[scheme]="widget6.scheme"
[results]="widgets.widget6.mainChart[widget6.currentRange]"
[legend]="widget6.showLegend"
[explodeSlices]="widget6.explodeSlices"
[labels]="widget6.labels"
[doughnut]="widget6.doughnut"
[gradient]="widget6.gradient"
(select)="widget6.onSelect($event)">
</ngx-charts-pie-chart>
</div>
<div class="py-8 mh-16 border-top" fxLayout="row" fxLayoutAlign="start center" fxLayoutWrap>
<div class="py-8 border-right" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">{{widgets.widget6.footerLeft.count[widget6.currentRange]}}</span>
<span class="h4">{{widgets.widget6.footerLeft.title}}</span>
</div>
<div class="py-8" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" fxFlex.gt-sm="50">
<span class="mat-display-1 mb-0">{{widgets.widget6.footerRight.count[widget6.currentRange]}}</span>
<span class="h4">{{widgets.widget6.footerRight.title}}</span>
</div>
</div>
</div>
<!-- / Front -->
</fuse-widget>
<!-- / WIDGET 6 -->
<!-- WIDGET 7 -->
<fuse-widget [@animate]="{value:'*',params:{y:'100%'}}" class="widget" fxLayout="column" fxFlex="100" fxFlex.gt-sm="50">
<!-- Front -->
<div class="fuse-widget-front mat-white-bg mat-elevation-z2">
<div class="px-16 border-bottom" fxLayout="row" fxLayoutAlign="space-between center">
<div class="h3">{{widgets.widget7.title}}</div>
<mat-form-field>
<mat-select class="simplified" [(ngModel)]="widget7.currentRange"
aria-label="Change range">
<mat-option *ngFor="let range of widgets.widget7.ranges | keys"
[value]="range.key">
{{range.value}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="p-16" fxLayout="row" fxLayoutAlign="space-between center"
*ngFor="let customer of widgets.widget7.customers[widget7.currentRange]">
<div>
<div class="h3">{{customer.name}}</div>
<div>
<span *ngIf="customer.location">{{customer.location}}</span>
</div>
</div>
<button mat-icon-button aria-label="More information">
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
<!-- / Front -->
</fuse-widget>
<!-- / WIDGET 7 -->
</div>
<!-- / WIDGET GROUP -->
</div>
<!-- / CONTENT -->
</div>

View File

@ -0,0 +1,7 @@
#e-commerce-dashboard {
.content {
flex: 1 0 auto;
}
}

View File

@ -0,0 +1,93 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { EcommerceDashboardService } from './dashboard.service';
import * as shape from 'd3-shape';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { DataSource } from '@angular/cdk/collections';
import { fuseAnimations } from '../../../../../core/animations';
@Component({
selector : 'fuse-e-commerce-dashboard',
templateUrl : './dashboard.component.html',
styleUrls : ['./dashboard.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
})
export class FuseEcommerceDashboardComponent implements OnInit, OnDestroy
{
projects: any[];
selectedProject: any;
widgets: any;
widget5: any = {};
widget6: any = {};
widget7: any = {};
dateNow = Date.now();
constructor(private projectsDashboardService: EcommerceDashboardService)
{
this.projects = this.projectsDashboardService.projects;
this.selectedProject = this.projects[0];
this.widgets = this.projectsDashboardService.widgets;
/**
* Widget 5
*/
this.widget5 = {
currentRange : 'TW',
xAxis : true,
yAxis : true,
gradient : false,
legend : false,
showXAxisLabel: false,
xAxisLabel : 'Days',
showYAxisLabel: false,
yAxisLabel : 'Isues',
scheme : {
domain: ['#42BFF7', '#C6ECFD', '#C7B42C', '#AAAAAA']
},
onSelect : (ev) => {
console.log(ev);
}
};
/**
* Widget 6
*/
this.widget6 = {
currentRange : 'TW',
legend : false,
explodeSlices: false,
labels : true,
doughnut : true,
gradient : false,
scheme : {
domain: ['#f44336', '#9c27b0', '#03a9f4', '#e91e63']
},
onSelect : (ev) => {
console.log(ev);
}
};
/**
* Widget 7
*/
this.widget7 = {
currentRange: 'T'
};
}
ngOnInit()
{
}
ngOnDestroy()
{
}
}

View File

@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class EcommerceDashboardService implements Resolve<any>
{
projects: any[];
widgets: any[];
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
return new Promise((resolve, reject) => {
Promise.all([
this.getProjects(),
this.getWidgets()
]).then(
() => {
resolve();
},
reject
);
});
}
getProjects(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/projects-dashboard-projects')
.subscribe((response: any) => {
this.projects = response;
resolve(response);
}, reject);
});
}
getWidgets(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/e-commerce-dashboard')
.subscribe((response: any) => {
this.widgets = response;
resolve(response);
}, reject);
});
}
}

View File

@ -0,0 +1,91 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { FuseEcommerceDashboardComponent } from './dashboard/dashboard.component';
import { EcommerceDashboardService } from './dashboard/dashboard.service';
import { SharedModule } from '../../../../core/modules/shared.module';
import { FuseWidgetModule } from '../../../../core/components/widget/widget.module';
import { FuseEcommerceProductsComponent } from './products/products.component';
import { EcommerceProductsService } from './products/products.service';
import { FuseEcommerceProductComponent } from './product/product.component';
import { EcommerceProductService } from './product/product.service';
import { FuseEcommerceOrdersComponent } from './orders/orders.component';
import { EcommerceOrdersService } from './orders/orders.service';
import { FuseEcommerceOrderComponent } from './order/order.component';
import { EcommerceOrderService } from './order/order.service';
import { AgmCoreModule } from '@agm/core';
const routes: Routes = [
{
path : 'dashboard',
component: FuseEcommerceDashboardComponent,
resolve : {
data: EcommerceDashboardService
}
},
{
path : 'products',
component: FuseEcommerceProductsComponent,
resolve : {
data: EcommerceProductsService
}
},
{
path : 'products/:id',
component: FuseEcommerceProductComponent,
resolve : {
data: EcommerceProductService
}
},
{
path : 'products/:id/:handle',
component: FuseEcommerceProductComponent,
resolve : {
data: EcommerceProductService
}
},
{
path : 'orders',
component: FuseEcommerceOrdersComponent,
resolve : {
data: EcommerceOrdersService
}
},
{
path : 'orders/:id',
component: FuseEcommerceOrderComponent,
resolve : {
data: EcommerceOrderService
}
}
];
@NgModule({
imports : [
SharedModule,
RouterModule.forChild(routes),
FuseWidgetModule,
NgxChartsModule,
AgmCoreModule.forRoot({
apiKey: 'AIzaSyD81ecsCj4yYpcXSLFcYU97PvRsE_X8Bx8'
})
],
declarations: [
FuseEcommerceDashboardComponent,
FuseEcommerceProductsComponent,
FuseEcommerceProductComponent,
FuseEcommerceOrdersComponent,
FuseEcommerceOrderComponent
],
providers : [
EcommerceDashboardService,
EcommerceProductsService,
EcommerceProductService,
EcommerceOrdersService,
EcommerceOrderService
]
})
export class FuseEcommerceModule
{
}

View File

@ -0,0 +1,72 @@
export const orderStatuses = [
{
'id' : 1,
'name' : 'Awaiting check payment',
'color': 'mat-blue-500-bg'
},
{
'id' : 2,
'name' : 'Payment accepted',
'color': 'mat-green-500-bg'
},
{
'id' : 3,
'name' : 'Preparing the order',
'color': 'mat-orange-500-bg'
},
{
'id' : 4,
'name' : 'Shipped',
'color': 'mat-purple-500-bg'
},
{
'id' : 5,
'name' : 'Delivered',
'color': 'mat-green-800-bg'
},
{
'id' : 6,
'name' : 'Canceled',
'color': 'mat-pink-500-bg'
},
{
'id' : 7,
'name' : 'Refunded',
'color': 'mat-red-500-bg'
},
{
'id' : 8,
'name' : 'Payment error',
'color': 'mat-red-900-bg'
},
{
'id' : 9,
'name' : 'On pre-order (paid)',
'color': 'mat-purple-300-bg'
},
{
'id' : 10,
'name' : 'Awaiting bank wire payment',
'color': 'mat-blue-500-bg'
},
{
'id' : 11,
'name' : 'Awaiting PayPal payment',
'color': 'mat-blue-500-bg'
},
{
'id' : 12,
'name' : 'Remote payment accepted',
'color': 'mat-green-500-bg'
},
{
'id' : 13,
'name' : 'On pre-order (not paid)',
'color': 'mat-purple-300-bg'
},
{
'id' : 14,
'name' : 'Awaiting Cash-on-delivery payment',
'color': 'mat-blue-500-bg'
}
];

View File

@ -0,0 +1,429 @@
<div id="order" class="page-layout carded fullwidth" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<!-- CENTER -->
<div class="center">
<!-- HEADER -->
<div class="header white-fg"
fxLayout="row" fxLayoutAlign="space-between center">
<!-- APP TITLE -->
<div fxLayout="row" fxLayoutAlign="start center">
<button class="mr-16" mat-icon-button [routerLink]="'/apps/e-commerce/orders'">
<mat-icon>arrow_back</mat-icon>
</button>
<div fxLayout="column" fxLayoutAlign="start start"
*fuseIfOnDom [@animate]="{value:'*',params:{delay:'100ms',x:'-25px'}}">
<div class="h2">
Order {{order.reference}}
</div>
<div class="subtitle secondary-text">
<span>from</span>
<span>{{order.customer.firstName}} {{order.customer.lastName}}</span>
</div>
</div>
</div>
<!-- / APP TITLE -->
</div>
<!-- / HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg">
<!-- CONTENT -->
<div class="content">
<mat-tab-group>
<mat-tab label="Order Details">
<div class="order-details tab-content p-24" fusePerfectScrollbar>
<div class="section pb-48">
<div class="pb-16" fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="m-0 mr-16">account_circle</mat-icon>
<div class="h2 secondary-text">Customer</div>
</div>
<div class="customer">
<table class="simple">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Company</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div fxLayout="row" fxLayoutAlign="start center">
<img class="avatar" [src]="order.customer.avatar">
<span class="name text-truncate">{{order.customer.firstName}} {{order.customer.lastName}}</span>
</div>
</td>
<td>
<span class="email text-truncate">{{order.customer.email}}</span>
</td>
<td>
<span class="phone text-truncate">{{order.customer.phone}}</span>
</td>
<td>
<span class="company text-truncate">{{order.customer.company}}</span>
</td>
</tr>
</tbody>
</table>
</div>
<mat-tab-group class="addresses">
<mat-tab label="Shipping Address">
<div fxFlex fxLayout="column" *fuseIfOnDom>
<div class="address h4 py-24">{{order.customer.shippingAddress.address}}</div>
<agm-map class="w-100-p h-400" [zoom]="15"
[latitude]="order.customer.shippingAddress.lat"
[longitude]="order.customer.shippingAddress.lng">
<agm-marker [latitude]="order.customer.shippingAddress.lat"
[longitude]="order.customer.shippingAddress.lng">
</agm-marker>
</agm-map>
</div>
</mat-tab>
<mat-tab label="Invoice Address" fxLayout="column">
<div fxFlex fxLayout="column" *fuseIfOnDom>
<div class="address h4 py-24">{{order.customer.invoiceAddress.address}}</div>
<agm-map class="w-100-p h-400" [zoom]="15"
[latitude]="order.customer.invoiceAddress.lat"
[longitude]="order.customer.invoiceAddress.lng">
<agm-marker [latitude]="order.customer.invoiceAddress.lat"
[longitude]="order.customer.invoiceAddress.lng">
</agm-marker>
</agm-map>
</div>
</mat-tab>
</mat-tab-group>
</div>
<div class="section pb-48">
<div class="pb-16" fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="m-0 mr-16">access_time</mat-icon>
<div class="h2 secondary-text">Order Status</div>
</div>
<table class="simple">
<thead>
<tr>
<th>Status</th>
<th>Updated On</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let status of order.status">
<td>
<span class="status h6 p-4" [ngClass]="status.color">
{{status.name}}
</span>
</td>
<td>
<span>
{{status.date | date}}
</span>
</td>
</tr>
</tbody>
</table>
<form class="update-status p-24"
(ngSubmit)="updateStatus()"
[formGroup]="statusForm"
fxLayout="row" fxLayoutAlign="start center">
<mat-form-field class="mr-16" fxFlex>
<mat-select formControlName="newStatus"
placeholder="Select a status" required>
<mat-option *ngFor="let status of orderStatuses"
[value]="status.id">
{{status.name}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button class="mat-accent"
[disabled]="statusForm.invalid">
Update Status
</button>
</form>
</div>
<div class="section pb-48">
<div class="pb-16" fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="m-0 mr-16">attach_money</mat-icon>
<div class="h2 secondary-text">Payment</div>
</div>
<table class="simple">
<thead>
<tr>
<th>TransactionID</th>
<th>Payment Method</th>
<th>Amount</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="text-truncate">
{{order.payment.transactionId}}
</span>
</td>
<td>
<span class="text-truncate">
{{order.payment.method}}
</span>
</td>
<td>
<span class="text-truncate">
{{order.payment.amount}}
</span>
</td>
<td>
<span class="text-truncate">
{{order.payment.date | date}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="section pb-48">
<div class="pb-16" fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="m-0 mr-16">local_shipping</mat-icon>
<div class="h2 secondary-text">Shipping</div>
</div>
<table class="simple">
<thead>
<tr>
<th>Tracking Code</th>
<th>Carrier</th>
<th>Weight</th>
<th>Fee</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let shipping of order.shippingDetails">
<td class="tracking-code">
{{shipping.tracking}}
</td>
<td>
{{shipping.carrier}}
</td>
<td>
{{shipping.weight}}
</td>
<td>
{{shipping.fee}}
</td>
<td>
{{shipping.date}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</mat-tab>
<mat-tab label="Products">
<div class="products tab-content p-24" fusePerfectScrollbar>
<table class="simple">
<thead>
<tr>
<th>ID</th>
<th>Image</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
<tr class="product-row"
*ngFor="let product of order.products"
[routerLink]="'/apps/e-commerce/products/'+product.id+'/'+product.handle">
<td class="w-64">
{{product.id}}
</td>
<td class="w-80">
<img class="product-image" [src]="product.image">
</td>
<td>
{{product.name}}
</td>
<td>
{{product.price}}
</td>
<td>
{{product.quantity}}
</td>
</tr>
</tbody>
</table>
</div>
</mat-tab>
<mat-tab label="Invoice">
<div class="invoice tab-content p-24" fusePerfectScrollbar>
<div id="invoice" class="compact page-layout blank" fxLayout="row" fusePerfectScrollbar>
<div class="invoice-container">
<!-- INVOICE -->
<div class="card">
<div class="header">
<div class="invoice-date">{{order.date}}</div>
<div fxLayout="row" fxLayoutAlign="space-between stretch">
<div class="client">
<div class="invoice-number" fxLayout="row" fxLayoutAlign="start center">
<span class="title">INVOICE</span>
<span class="number">{{order.reference}}</span>
</div>
<div class="info">
<div class="title">
{{order.customer.firstName}}
{{order.customer.lastName}}
</div>
<div class="address">{{order.customer.invoiceAddress}}</div>
<div class="phone">{{order.customer.phone}}</div>
<div class="email">{{order.customer.email}}</div>
</div>
</div>
<div class="issuer mat-accent-bg" fxLayout="row" fxLayoutAlign="start center">
<div class="logo">
<img src="assets/images/logos/fuse.svg">
</div>
<div class="info">
<div class="title">FUSE INC.</div>
<div class="address">2810 Country Club Road Cranford, NJ 07016</div>
<div class="phone">+66 123 455 87</div>
<div class="email">hello@fuseinc.com</div>
<div class="website">www.fuseinc.com</div>
</div>
</div>
</div>
</div>
<div class="content">
<table class="simple invoice-table">
<thead>
<tr>
<th>PRODUCT</th>
<th class="text-right">PRICE</th>
<th class="text-right">QUANTITY</th>
<th class="text-right">TOTAL</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of order.products">
<td>
<div class="title">
{{product.name}}
</div>
</td>
<td class="text-right">
{{product.price | currency:'USD':true}}
</td>
<td class="text-right">
{{product.quantity}}
</td>
<td class="text-right">
{{product.total | currency:'USD':true}}
</td>
</tr>
</tbody>
</table>
<table class="simple invoice-table-footer">
<tbody>
<tr class="subtotal">
<td>SUBTOTAL</td>
<td>{{order.subtotal | currency:'USD':true}}</td>
</tr>
<tr class="tax">
<td>TAX</td>
<td>{{order.tax | currency:'USD':true}}</td>
</tr>
<tr class="discount">
<td>DISCOUNT</td>
<td>-{{order.discount | currency:'USD':true}}</td>
</tr>
<tr class="total">
<td>TOTAL</td>
<td>{{order.total | currency:'USD':true}}</td>
</tr>
</tbody>
</table>
</div>
<div class="footer">
<div class="note">Please pay within 15 days. Thank you for your business.</div>
<div fxLayout="row" fxLayoutAlign="start start">
<div class="logo">
<img src="assets/images/logos/fuse.svg">
</div>
<div class="small-note">
In condimentum malesuada efficitur. Mauris volutpat placerat auctor. Ut ac congue dolor. Quisque
scelerisque lacus sed feugiat fermentum. Cras aliquet facilisis pellentesque. Nunc hendrerit
quam at leo commodo, a suscipit tellus dapibus. Etiam at felis volutpat est mollis lacinia.
Mauris placerat sem sit amet velit mollis, in porttitor ex finibus. Proin eu nibh id libero
tincidunt lacinia et eget eros.
</div>
</div>
</div>
</div>
<!-- / INVOICE -->
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
<!-- / CONTENT -->
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</div>

View File

@ -0,0 +1,395 @@
@import "src/app/core/scss/fuse";
#order {
.header {
.subtitle {
margin: 6px 0 0 0;
}
}
.content {
.mat-tab-group,
.mat-tab-body-wrapper,
.tab-content {
flex: 1 1 auto;
max-width: 100%;
}
.tab-content {
flex: 1 1 auto;
&.products {
.product-row {
cursor: pointer;
}
}
&.invoice {
#invoice {
&.compact {
padding: 0;
overflow: auto;
.invoice-container {
padding: 64px;
.card {
width: 1020px;
min-width: 1020px;
max-width: 1020px;
padding: 64px 88px;
overflow: hidden;
background: #FFFFFF;
@include mat-elevation(7);
.header {
.invoice-date {
font-size: 14px;
color: rgba(0, 0, 0, 0.54);
margin-bottom: 32px;
}
.client {
.invoice-number {
font-size: 18px;
padding-bottom: 2px;
.title {
color: rgba(0, 0, 0, 0.54);
}
.number {
padding-left: 6px;
}
}
.due-date {
font-size: 18px;
padding-bottom: 16px;
.title {
color: rgba(0, 0, 0, 0.54);
}
.date {
padding-left: 6px;
}
}
.info {
color: rgba(0, 0, 0, 0.54);
line-height: 22px;
}
}
.issuer {
margin-right: -88px;
padding-right: 66px;
.logo {
width: 96px;
padding: 0 8px;
border-right: 1px solid rgba(255, 255, 255, 0.7);
}
.info {
padding: 16px;
}
}
}
.content {
.invoice-table {
margin-top: 64px;
font-size: 15px;
thead {
tr {
th {
&:first-child {
padding-left: 8px;
}
&:last-child {
padding-right: 8px;
}
}
}
}
tbody {
tr {
td {
&:first-child {
padding-left: 8px;
}
&:last-child {
padding-right: 8px;
}
}
}
}
.title {
font-size: 16px;
}
.detail {
margin-top: 8px;
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
max-width: 360px;
}
}
.invoice-table-footer {
margin: 32px 0 72px 0;
tr {
td {
text-align: right;
font-size: 16px;
font-weight: 500;
color: rgba(0, 0, 0, 0.54);
border-bottom: none;
padding: 4px 8px;
&:first-child {
text-align: left;
}
}
&.discount {
td {
padding-bottom: 32px;
}
}
&.total {
td {
padding: 24px 8px;
border-top: 1px solid rgba(0, 0, 0, 0.12);
font-size: 35px;
font-weight: 300;
color: rgba(0, 0, 0, 1);
}
}
}
}
}
.footer {
.note {
font-size: 15px;
font-weight: 500;
margin-bottom: 24px;
}
// IE10 fix
.logo, .small-note {
-ms-flex: 0 1 auto;
}
.logo {
width: 32px;
min-width: 32px;
margin-right: 24px;
}
.small-note {
font-size: 12px;
font-weight: 500;
color: rgba(0, 0, 0, 0.54);
line-height: 18px;
}
}
}
}
}
}
/* PRINT STYLES */
@media print {
/* Invoice Specific Styles */
#invoice {
&.compact {
.invoice-container {
padding: 0;
.card {
width: 100%;
min-width: 0;
background: none;
padding: 0;
box-shadow: none;
.header {
.invoice-date {
margin-bottom: 16pt;
}
.issuer {
padding-right: 0;
margin-right: 0;
}
}
.content {
.invoice-table {
margin-top: 16pt;
thead {
tr {
th {
font-size: 10pt;
max-width: 60pt;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
}
tbody {
tr {
td {
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
}
.title {
font-size: 10pt;
}
.detail {
margin-top: 4pt;
font-size: 9pt;
max-width: none;
}
}
.invoice-table-footer {
margin: 16pt 0;
tr {
td {
font-size: 13pt;
padding: 4pt 4pt;
&:first-child {
text-align: left;
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
&.discount {
td {
padding-bottom: 16pt;
}
}
&.total {
td {
padding: 16pt 4pt 0 4pt;
font-size: 16pt;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
}
}
}
.footer {
.note {
font-size: 10pt;
margin-bottom: 8pt;
}
.logo {
margin-right: 8pt;
}
.small-note {
font-size: 8pt;
line-height: normal;
}
}
}
}
}
}
}
}
}
.mat-tab-body-content {
display: flex;
}
.mat-tab-label {
height: 64px;
}
table {
table-layout: fixed;
}
}
}

View File

@ -0,0 +1,78 @@
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { EcommerceOrderService } from './order.service';
import { fuseAnimations } from '../../../../../core/animations';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { Subscription } from 'rxjs/Subscription';
import { Order } from './order.model';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FuseUtils } from '../../../../../core/fuseUtils';
import { MatSnackBar } from '@angular/material';
import { Location } from '@angular/common';
import { orderStatuses } from './order-statuses';
@Component({
selector : 'fuse-e-commerce-order',
templateUrl : './order.component.html',
styleUrls : ['./order.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
})
export class FuseEcommerceOrderComponent implements OnInit, OnDestroy
{
order = new Order();
onOrderChanged: Subscription;
statusForm: FormGroup;
orderStatuses = orderStatuses;
constructor(
private orderService: EcommerceOrderService,
private formBuilder: FormBuilder,
public snackBar: MatSnackBar,
private location: Location
)
{
}
ngOnInit()
{
// Subscribe to update order on changes
this.onOrderChanged =
this.orderService.onOrderChanged
.subscribe(order => {
this.order = new Order(order);
});
this.statusForm = this.formBuilder.group({
newStatus: ['']
});
}
updateStatus()
{
const newStatusId = Number.parseInt(this.statusForm.get('newStatus').value);
if ( !newStatusId )
{
return;
}
const newStatus = this.orderStatuses.find((status) => {
return status.id === newStatusId;
});
newStatus['date'] = new Date().toString();
this.order.status.unshift(newStatus);
}
ngOnDestroy()
{
this.onOrderChanged.unsubscribe();
}
}

View File

@ -0,0 +1,34 @@
import { FuseUtils } from '../../../../../core/fuseUtils';
export class Order
{
id: string;
reference: string;
subtotal: string;
tax: string;
discount: string;
total: string;
date: string;
customer: any;
products: any[];
status: any[];
payment: any;
shippingDetails: any[];
constructor(order?)
{
order = order || {};
this.id = order.id || FuseUtils.generateGUID();
this.reference = order.reference || FuseUtils.generateGUID();
this.subtotal = order.subtotal || 0;
this.tax = order.tax || 0;
this.discount = order.discount || 0;
this.total = order.total || 0;
this.date = order.date || '';
this.customer = order.customer || {};
this.products = order.products || [];
this.status = order.status || [];
this.payment = order.payment || {};
this.shippingDetails = order.shippingDetails || [];
}
}

View File

@ -0,0 +1,75 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class EcommerceOrderService implements Resolve<any>
{
routeParams: any;
order: any;
onOrderChanged: BehaviorSubject<any> = new BehaviorSubject({});
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
this.routeParams = route.params;
return new Promise((resolve, reject) => {
Promise.all([
this.getOrder()
]).then(
() => {
resolve();
},
reject
);
});
}
getOrder(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/e-commerce-orders/' + this.routeParams.id)
.subscribe((response: any) => {
this.order = response;
this.onOrderChanged.next(this.order);
resolve(response);
}, reject);
});
}
saveOrder(order)
{
return new Promise((resolve, reject) => {
this.http.post('api/e-commerce-orders/' + order.id, order)
.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
addOrder(order)
{
return new Promise((resolve, reject) => {
this.http.post('api/e-commerce-orders/', order)
.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
}

View File

@ -0,0 +1,133 @@
<div id="orders" class="page-layout carded fullwidth" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<!-- CENTER -->
<div class="center">
<!-- HEADER -->
<div class="header white-fg"
fxLayout="column" fxLayoutAlign="center center"
fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="space-between center">
<!-- APP TITLE -->
<div class="logo"
fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="logo-icon mr-16" *fuseIfOnDom [@animate]="{value:'*',params:{delay:'50ms',scale:'0.2'}}">shopping_basket</mat-icon>
<span class="logo-text h1" *fuseIfOnDom [@animate]="{value:'*',params:{delay:'100ms',x:'-25px'}}">Orders</span>
</div>
<!-- / APP TITLE -->
<!-- SEARCH -->
<div class="search-input-wrapper ml-8 m-sm-0"
fxFlex="1 0 auto" fxLayout="row" fxLayoutAlign="start center">
<label for="search" class="mr-8">
<mat-icon class="secondary-text">search</mat-icon>
</label>
<mat-form-field floatPlaceholder="never" fxFlex="1 0 auto">
<input id="search" matInput #filter placeholder="Search">
</mat-form-field>
</div>
<!-- / SEARCH -->
</div>
<!-- / HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg">
<mat-table class="orders-table"
#table [dataSource]="dataSource"
matSort
[@animateStagger]="{value:'50'}"
fusePerfectScrollbar>
<!-- ID Column -->
<ng-container cdkColumnDef="id">
<mat-header-cell *cdkHeaderCellDef mat-sort-header>ID</mat-header-cell>
<mat-cell *cdkCellDef="let order">
<p class="text-truncate">{{order.id}}</p>
</mat-cell>
</ng-container>
<!-- Reference Column -->
<ng-container cdkColumnDef="reference">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-sm>Reference</mat-header-cell>
<mat-cell *cdkCellDef="let order" fxHide fxShow.gt-sm>
<p class="text-truncate">{{order.reference}}</p>
</mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container cdkColumnDef="customer">
<mat-header-cell *cdkHeaderCellDef mat-sort-header>Customer</mat-header-cell>
<mat-cell *cdkCellDef="let order">
<p class="text-truncate">
{{order.customer.firstName}}
{{order.customer.lastName}}
</p>
</mat-cell>
</ng-container>
<!-- Total Price Column -->
<ng-container cdkColumnDef="total">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-md>Total</mat-header-cell>
<mat-cell *cdkCellDef="let order" fxHide fxShow.gt-md>
<p class="total-price text-truncate">
{{order.total | currency:'USD':true}}
</p>
</mat-cell>
</ng-container>
<!-- Payment Column -->
<ng-container cdkColumnDef="payment">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-sm>Payment</mat-header-cell>
<mat-cell *cdkCellDef="let order" fxHide fxShow.gt-sm>
<p class="text-truncate">
{{order.payment.method}}
</p>
</mat-cell>
</ng-container>
<!-- Status Column -->
<ng-container cdkColumnDef="status">
<mat-header-cell *cdkHeaderCellDef mat-sort-header >Status</mat-header-cell>
<mat-cell *cdkCellDef="let order">
<p class="status text-truncate h6 p-4" [ngClass]="order.status[0].color">
{{order.status[0].name}}
</p>
</mat-cell>
</ng-container>
<!-- Date Column -->
<ng-container cdkColumnDef="date">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-sm>Date</mat-header-cell>
<mat-cell *cdkCellDef="let order" fxHide fxShow.gt-sm>
<p class="text-truncate">
{{order.date}}
</p>
</mat-cell>
</ng-container>
<mat-header-row *cdkHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *cdkRowDef="let order; columns: displayedColumns;"
class="order"
matRipple
[routerLink]="'/apps/e-commerce/orders/'+order.id">
</mat-row>
</mat-table>
<mat-paginator #paginator
[length]="dataSource.filteredData.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 25, 100]">
</mat-paginator>
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</div>

View File

@ -0,0 +1,73 @@
:host {
.header {
.search-input-wrapper {
max-width: 480px;
}
}
.mat-tab-group,
.mat-tab-body-wrapper,
.tab-content{
flex: 1 1 auto;
max-width: 100%;
}
.orders-table {
flex: 1 1 auto;
border-bottom: 1px solid rgba(0, 0, 0, .12);
.mat-header-row {
min-height: 64px;
}
.order {
position: relative;
cursor: pointer;
height: 84px;
}
.mat-cell {
min-width: 0;
display: flex;
align-items: center;
}
.mat-column-id {
flex: 0 1 84px;
}
.mat-column-image {
flex: 0 1 84px;
.product-image {
width: 52px;
height: 52px;
border: 1px solid rgba(0, 0, 0, .12);
}
}
.mat-column-buttons {
flex: 0 1 80px;
}
.quantity-indicator {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
border-radius: 4px;
margin-right: 8px;
& + span {
display: inline-block;
vertical-align: middle;
}
}
.active-icon {
border-radius: 50%;
}
}
}

View File

@ -0,0 +1,169 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { EcommerceOrdersService } from './orders.service';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import { fuseAnimations } from '../../../../../core/animations';
import { MatPaginator, MatSort } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { FuseUtils } from '../../../../../core/fuseUtils';
@Component({
selector : 'fuse-e-commerce-orders',
templateUrl: './orders.component.html',
styleUrls : ['./orders.component.scss'],
animations : fuseAnimations
})
export class FuseEcommerceOrdersComponent implements OnInit
{
dataSource: FilesDataSource | null;
displayedColumns = ['id', 'reference', 'customer', 'total', 'payment', 'status', 'date'];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild('filter') filter: ElementRef;
@ViewChild(MatSort) sort: MatSort;
constructor(
private ordersService: EcommerceOrdersService
)
{
}
ngOnInit()
{
this.dataSource = new FilesDataSource(this.ordersService, this.paginator, this.sort);
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => {
if ( !this.dataSource )
{
return;
}
this.dataSource.filter = this.filter.nativeElement.value;
});
}
}
export class FilesDataSource extends DataSource<any>
{
_filterChange = new BehaviorSubject('');
_filteredDataChange = new BehaviorSubject('');
get filteredData(): any
{
return this._filteredDataChange.value;
}
set filteredData(value: any)
{
this._filteredDataChange.next(value);
}
get filter(): string
{
return this._filterChange.value;
}
set filter(filter: string)
{
this._filterChange.next(filter);
}
constructor(
private ordersService: EcommerceOrdersService,
private _paginator: MatPaginator,
private _sort: MatSort
)
{
super();
this.filteredData = this.ordersService.orders;
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<any[]>
{
const displayDataChanges = [
this.ordersService.onOrdersChanged,
this._paginator.page,
this._filterChange,
this._sort.sortChange
];
return Observable.merge(...displayDataChanges).map(() => {
let data = this.ordersService.orders.slice();
data = this.filterData(data);
this.filteredData = [...data];
data = this.sortData(data);
// Grab the page's slice of data.
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
return data.splice(startIndex, this._paginator.pageSize);
});
}
filterData(data)
{
if ( !this.filter )
{
return data;
}
return FuseUtils.filterArrayByString(data, this.filter);
}
sortData(data): any[]
{
if ( !this._sort.active || this._sort.direction === '' )
{
return data;
}
return data.sort((a, b) => {
let propertyA: number | string = '';
let propertyB: number | string = '';
switch ( this._sort.active )
{
case 'id':
[propertyA, propertyB] = [a.id, b.id];
break;
case 'reference':
[propertyA, propertyB] = [a.reference, b.reference];
break;
case 'customer':
[propertyA, propertyB] = [a.customer.firstName, b.customer.firstName];
break;
case 'total':
[propertyA, propertyB] = [a.total, b.total];
break;
case 'payment':
[propertyA, propertyB] = [a.payment.method, b.payment.method];
break;
case 'status':
[propertyA, propertyB] = [a.status[0].name, b.status[0].name];
break;
case 'date':
[propertyA, propertyB] = [a.date, b.date];
break;
}
const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
const valueB = isNaN(+propertyB) ? propertyB : +propertyB;
return (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1);
});
}
disconnect()
{
}
}

View File

@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class EcommerceOrdersService implements Resolve<any>
{
orders: any[];
onOrdersChanged: BehaviorSubject<any> = new BehaviorSubject({});
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
return new Promise((resolve, reject) => {
Promise.all([
this.getOrders()
]).then(
() => {
resolve();
},
reject
);
});
}
getOrders(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/e-commerce-orders')
.subscribe((response: any) => {
this.orders = response;
this.onOrdersChanged.next(this.orders);
resolve(response);
}, reject);
});
}
}

View File

@ -0,0 +1,255 @@
<div id="product" class="page-layout carded fullwidth" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<!-- CENTER -->
<div class="center">
<!-- HEADER -->
<div class="header white-fg" fxLayout="row" fxLayoutAlign="space-between center">
<!-- APP TITLE -->
<div fxLayout="row" fxLayoutAlign="start center">
<button class="mr-0 mr-sm-16" mat-icon-button [routerLink]="'/apps/e-commerce/products'">
<mat-icon>arrow_back</mat-icon>
</button>
<div class="product-image mr-8 mr-sm-16" *fuseIfOnDom [@animate]="{value:'*',params:{delay:'50ms',scale:'0.2'}}">
<img *ngIf="product.images[0]" [src]="product.images[0].url">
<img *ngIf="!product.images[0]" [src]="'assets/images/ecommerce/product-image-placeholder.png'">
</div>
<div fxLayout="column" fxLayoutAlign="start start"
*fuseIfOnDom [@animate]="{value:'*',params:{delay:'100ms',x:'-25px'}}">
<div class="h2" *ngIf="pageType ==='edit'">
{{product.name}}
</div>
<div class="h2" *ngIf="pageType ==='new'">
New Product
</div>
<div class="subtitle secondary-text">
<span>Product Detail</span>
</div>
</div>
</div>
<!-- / APP TITLE -->
<button mat-raised-button
class="save-product-button mat-white-bg mt-16 mt-sm-0"
[disabled]="productForm.invalid"
*ngIf="pageType ==='new'" (click)="addProduct()">
<span>ADD</span>
</button>
<button mat-raised-button
class="save-product-button mat-white-bg mt-16 mt-sm-0"
[disabled]="productForm.invalid || productForm.pristine"
*ngIf="pageType ==='edit'" (click)="saveProduct()">
<span>SAVE</span>
</button>
</div>
<!-- / HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg">
<!-- CONTENT -->
<div class="content">
<form name="productForm" [formGroup]="productForm" class="product w-100-p" fxLayout="column" fxFlex>
<mat-tab-group>
<mat-tab label="Basic Info">
<div class="tab-content p-24" fusePerfectScrollbar>
<mat-form-field class="w-100-p">
<input matInput
name="name"
formControlName="name"
placeholder="Product Name"
required>
</mat-form-field>
<mat-form-field class="w-100-p">
<textarea matInput
name="description"
formControlName="description"
placeholder="Product Description"
rows="10">
</textarea>
</mat-form-field>
<h3 class="mb-0">Categories</h3>
<mat-form-field class="w-100-p" floatPlaceholder="never">
<mat-chip-list matPrefix #categoryList
name="categories"
formControlName="categories">
<mat-chip *ngFor="let category of product.categories"
removable="true" (remove)="product.removeCategory(category)">
{{category}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
<input matInput
matChipInputAddOnBlur="true"
(matChipInputTokenEnd)="product.addCategory($event)"
placeholder="Add category..."
[matChipInputFor]="categoryList"/>
</mat-form-field>
<h3 class="mb-0">Tags</h3>
<mat-form-field class="w-100-p" floatPlaceholder="never">
<mat-chip-list matPrefix #tagList
name="tags"
formControlName="tags">
<mat-chip *ngFor="let tag of product.tags"
removable="true" (remove)="product.removeTag(tag)">
{{tag}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
<input matInput
matChipInputAddOnBlur="true"
(matChipInputTokenEnd)="product.addTag($event)"
placeholder="Add tag..."
[matChipInputFor]="tagList"/>
</mat-form-field>
</div>
</mat-tab>
<mat-tab label="Product Images">
<div class="tab-content p-24" fusePerfectScrollbar>
<div fxLayout="row" fxLayoutAlign="start start" fxLayoutWrap>
<div *ngIf="product.images.length === 0"
class="product-image" fxlayout="row" fxLayoutAlign="center center">
<img class="media" [src]="'assets/images/ecommerce/product-image-placeholder.png'">
</div>
<div *ngFor="let image of product.images">
<div *ngIf="product.images.length > 0"
class="product-image" fxlayout="row" fxLayoutAlign="center center">
<img class="media" [src]="image.url">
</div>
</div>
</div>
</div>
</mat-tab>
<mat-tab label="Pricing">
<div class="tab-content p-24" fusePerfectScrollbar>
<mat-form-field class="w-100-p">
<input matInput
name="priceTaxExcl"
formControlName="priceTaxExcl"
placeholder="Tax Excluded Price" type="number">
<span matPrefix>$&nbsp;</span>
</mat-form-field>
<mat-form-field class="w-100-p">
<input matInput
name="priceTaxIncl"
formControlName="priceTaxIncl"
placeholder="Tax Included Price" type="number">
<span matPrefix>$&nbsp;</span>
</mat-form-field>
<mat-form-field class="w-100-p">
<input matInput
name="taxRate"
formControlName="taxRate"
placeholder="Tax Rate" type="number">
<span matPrefix>&#37;</span>
</mat-form-field>
<mat-form-field class="w-100-p">
<input matInput
name="comparedPrice"
formControlName="comparedPrice"
placeholder="Compared Price" type="number">
<span matPrefix>$&nbsp;</span>
<mat-hint align="start">Add a compare price to show next to the real price</mat-hint>
</mat-form-field>
</div>
</mat-tab>
<mat-tab label="Inventory">
<div class="tab-content p-24" fusePerfectScrollbar>
<mat-form-field class="w-100-p">
<input matInput
name="sku"
formControlName="sku"
placeholder="SKU">
</mat-form-field>
<mat-form-field class="w-100-p">
<input matInput
name="quantity"
formControlName="quantity"
placeholder="Quantity" type="number">
</mat-form-field>
</div>
</mat-tab>
<mat-tab label="Shipping">
<div class="tab-content p-24" fusePerfectScrollbar fxLayout="column">
<div class="py-16" fxFlex="1 0 auto" fxLayout="row">
<mat-form-field fxFlex>
<input matInput
name="Width"
formControlName="width"
placeholder="Width">
</mat-form-field>
<mat-form-field fxFlex>
<input matInput
name="Height"
formControlName="height"
placeholder="Height">
</mat-form-field>
<mat-form-field fxFlex>
<input matInput
name="Depth"
formControlName="depth"
placeholder="Depth">
</mat-form-field>
</div>
<mat-form-field class="w-100-p">
<input matInput
name="Weight"
formControlName="weight"
placeholder="Weight">
</mat-form-field>
<mat-form-field class="w-100-p">
<input matInput
name="extraShippingFee"
formControlName="extraShippingFee"
placeholder="Extra Shipping Fee" type="number">
<span matPrefix>$&nbsp;</span>
</mat-form-field>
</div>
</mat-tab>
</mat-tab-group>
</form>
</div>
<!-- / CONTENT -->
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</div>

View File

@ -0,0 +1,57 @@
#product {
.header {
.product-image {
overflow: hidden;
width: 56px;
height: 56px;
border: 3px solid rgba(0, 0, 0, 0.12);
img {
height: 100%;
width: auto;
max-width: none;
}
}
.subtitle {
margin: 6px 0 0 0;
}
}
.content {
.mat-tab-group,
.mat-tab-body-wrapper,
.tab-content{
flex: 1 1 auto;
max-width: 100%;
}
.mat-tab-body-content {
display: flex;
}
.mat-tab-label {
height: 64px;
}
.product-image {
overflow: hidden;
width: 128px;
height: 128px;
margin-right: 16px;
margin-bottom: 16px;
border: 3px solid rgba(0, 0, 0, 0.12);
img {
height: 100%;
width: auto;
max-width: none;
}
}
}
}

View File

@ -0,0 +1,132 @@
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { EcommerceProductService } from './product.service';
import { fuseAnimations } from '../../../../../core/animations';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { Subscription } from 'rxjs/Subscription';
import { Product } from './product.model';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FuseUtils } from '../../../../../core/fuseUtils';
import { MatSnackBar } from '@angular/material';
import { Location } from '@angular/common';
@Component({
selector : 'fuse-e-commerce-product',
templateUrl : './product.component.html',
styleUrls : ['./product.component.scss'],
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
})
export class FuseEcommerceProductComponent implements OnInit, OnDestroy
{
product = new Product();
onProductChanged: Subscription;
pageType: string;
productForm: FormGroup;
constructor(
private productService: EcommerceProductService,
private formBuilder: FormBuilder,
public snackBar: MatSnackBar,
private location: Location
)
{
}
ngOnInit()
{
// Subscribe to update product on changes
this.onProductChanged =
this.productService.onProductChanged
.subscribe(product => {
if ( product )
{
this.product = new Product(product);
this.pageType = 'edit';
}
else
{
this.pageType = 'new';
this.product = new Product();
}
this.productForm = this.createProductForm();
});
}
createProductForm()
{
return this.formBuilder.group({
id : [this.product.id],
name : [this.product.name],
handle : [this.product.handle],
description : [this.product.description],
categories : [this.product.categories],
tags : [this.product.tags],
images : [this.product.images],
priceTaxExcl : [this.product.priceTaxExcl],
priceTaxIncl : [this.product.priceTaxIncl],
taxRate : [this.product.taxRate],
comparedPrice : [this.product.comparedPrice],
quantity : [this.product.quantity],
sku : [this.product.sku],
width : [this.product.width],
height : [this.product.height],
depth : [this.product.depth],
weight : [this.product.weight],
extraShippingFee: [this.product.extraShippingFee],
active : [this.product.active]
});
}
saveProduct()
{
const data = this.productForm.getRawValue();
data.handle = FuseUtils.handleize(data.name);
this.productService.saveProduct(data)
.then(() => {
// Trigger the subscription with new data
this.productService.onProductChanged.next(data);
// Show the success message
this.snackBar.open('Product saved', 'OK', {
verticalPosition: 'top',
duration : 2000
});
});
}
addProduct()
{
const data = this.productForm.getRawValue();
data.handle = FuseUtils.handleize(data.name);
this.productService.addProduct(data)
.then(() => {
// Trigger the subscription with new data
this.productService.onProductChanged.next(data);
// Show the success message
this.snackBar.open('Product added', 'OK', {
verticalPosition: 'top',
duration : 2000
});
// Change the location with new one
this.location.go('apps/e-commerce/products/' + this.product.id + '/' + this.product.handle);
});
}
ngOnDestroy()
{
this.onProductChanged.unsubscribe();
}
}

View File

@ -0,0 +1,110 @@
import { FuseUtils } from '../../../../../core/fuseUtils';
import { MatChipInputEvent } from '@angular/material';
export class Product
{
id: string;
name: string;
handle: string;
description: string;
categories: string[];
tags: string[];
images: {
default: boolean,
id: string,
url: string,
type: string
}[];
priceTaxExcl: number;
priceTaxIncl: number;
taxRate: number;
comparedPrice: number;
quantity: number;
sku: string;
width: string;
height: string;
depth: string;
weight: string;
extraShippingFee: number;
active: boolean;
constructor(product?)
{
product = product || {};
this.id = product.id || FuseUtils.generateGUID();
this.name = product.name || '';
this.handle = product.handle || FuseUtils.handleize(this.name);
this.description = product.description || '';
this.categories = product.categories || [];
this.tags = product.tags || [];
this.images = product.images || [];
this.priceTaxExcl = product.priceTaxExcl || 0;
this.priceTaxIncl = product.priceTaxIncl || 0;
this.taxRate = product.taxRate || 0;
this.comparedPrice = product.comparedPrice || 0;
this.quantity = product.quantity || 0;
this.sku = product.sku || 0;
this.width = product.width || 0;
this.height = product.height || 0;
this.depth = product.depth || 0;
this.weight = product.weight || 0;
this.extraShippingFee = product.extraShippingFee || 0;
this.active = product.active || true;
}
addCategory(event: MatChipInputEvent): void
{
const input = event.input;
const value = event.value;
// Add category
if ( value )
{
this.categories.push(value);
}
// Reset the input value
if ( input )
{
input.value = '';
}
}
removeCategory(category)
{
const index = this.categories.indexOf(category);
if ( index >= 0 )
{
this.categories.splice(index, 1);
}
}
addTag(event: MatChipInputEvent): void
{
const input = event.input;
const value = event.value;
// Add tag
if ( value )
{
this.tags.push(value);
}
// Reset the input value
if ( input )
{
input.value = '';
}
}
removeTag(tag)
{
const index = this.tags.indexOf(tag);
if ( index >= 0 )
{
this.tags.splice(index, 1);
}
}
}

View File

@ -0,0 +1,83 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class EcommerceProductService implements Resolve<any>
{
routeParams: any;
product: any;
onProductChanged: BehaviorSubject<any> = new BehaviorSubject({});
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
this.routeParams = route.params;
return new Promise((resolve, reject) => {
Promise.all([
this.getProduct()
]).then(
() => {
resolve();
},
reject
);
});
}
getProduct(): Promise<any>
{
return new Promise((resolve, reject) => {
if ( this.routeParams.id === 'new' )
{
this.onProductChanged.next(false);
resolve(false);
}
else
{
this.http.get('api/e-commerce-products/' + this.routeParams.id)
.subscribe((response: any) => {
this.product = response;
this.onProductChanged.next(this.product);
resolve(response);
}, reject);
}
});
}
saveProduct(product)
{
return new Promise((resolve, reject) => {
this.http.post('api/e-commerce-products/' + product.id, product)
.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
addProduct(product)
{
return new Promise((resolve, reject) => {
this.http.post('api/e-commerce-products/', product)
.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
}

View File

@ -0,0 +1,145 @@
<div id="products" class="page-layout carded fullwidth" fusePerfectScrollbar>
<!-- TOP BACKGROUND -->
<div class="top-bg mat-accent-bg"></div>
<!-- / TOP BACKGROUND -->
<!-- CENTER -->
<div class="center">
<!-- HEADER -->
<div class="header white-fg"
fxLayout="column" fxLayoutAlign="center center"
fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="space-between center">
<!-- APP TITLE -->
<div class="logo my-12 m-sm-0"
fxLayout="row" fxLayoutAlign="start center">
<mat-icon class="logo-icon mr-16" *fuseIfOnDom [@animate]="{value:'*',params:{delay:'50ms',scale:'0.2'}}">shopping_basket</mat-icon>
<span class="logo-text h1" *fuseIfOnDom [@animate]="{value:'*',params:{delay:'100ms',x:'-25px'}}">Products</span>
</div>
<!-- / APP TITLE -->
<!-- SEARCH -->
<div class="search-input-wrapper mx-12 m-md-0"
fxFlex="1 0 auto" fxLayout="row" fxLayoutAlign="start center">
<label for="search" class="mr-8">
<mat-icon class="secondary-text">search</mat-icon>
</label>
<mat-form-field floatPlaceholder="never" fxFlex="1 0 auto">
<input id="search" matInput #filter placeholder="Search">
</mat-form-field>
</div>
<!-- / SEARCH -->
<button mat-raised-button
[routerLink]="'/apps/e-commerce/products/new'"
class="add-product-button mat-white-bg my-12 mt-sm-0">
<span>ADD NEW PRODUCT</span>
</button>
</div>
<!-- / HEADER -->
<!-- CONTENT CARD -->
<div class="content-card mat-white-bg">
<mat-table class="products-table"
#table [dataSource]="dataSource"
matSort
[@animateStagger]="{value:'50'}"
fusePerfectScrollbar>
<!-- ID Column -->
<ng-container cdkColumnDef="id">
<mat-header-cell *cdkHeaderCellDef mat-sort-header>ID</mat-header-cell>
<mat-cell *cdkCellDef="let product">
<p class="text-truncate">{{product.id}}</p>
</mat-cell>
</ng-container>
<!-- Image Column -->
<ng-container cdkColumnDef="image">
<mat-header-cell *cdkHeaderCellDef></mat-header-cell>
<mat-cell *cdkCellDef="let product">
<img class="product-image"
*ngIf="product.images[0]" [alt]="product.name"
[src]="product.images[0].url"/>
<img *ngIf="!product.images[0]" [src]="'assets/images/ecommerce/product-image-placeholder.png'">
</mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container cdkColumnDef="name">
<mat-header-cell *cdkHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *cdkCellDef="let product">
<p class="text-truncate">{{product.name}}</p>
</mat-cell>
</ng-container>
<!-- Category Column -->
<ng-container cdkColumnDef="category">
<mat-header-cell *cdkHeaderCellDef fxHide mat-sort-header fxShow.gt-md>Category</mat-header-cell>
<mat-cell *cdkCellDef="let product" fxHide fxShow.gt-md>
<p class="category text-truncate">
{{product.categories[0]}}
</p>
</mat-cell>
</ng-container>
<!-- Price Column -->
<ng-container cdkColumnDef="price">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-xs>Price</mat-header-cell>
<mat-cell *cdkCellDef="let product" fxHide fxShow.gt-xs>
<p class="price text-truncate">
{{product.priceTaxIncl | currency:'USD':true}}
</p>
</mat-cell>
</ng-container>
<!-- Quantity Column -->
<ng-container cdkColumnDef="quantity">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-sm>Quantity</mat-header-cell>
<mat-cell *cdkCellDef="let product" fxHide fxShow.gt-sm>
<span class="quantity-indicator text-truncate"
[ngClass]="{'mat-red-500-bg':product.quantity <= 5, 'mat-amber-500-bg':product.quantity > 5 && product.quantity <= 25,'mat-green-600-bg':product.quantity > 25}">
</span>
<span>
{{product.quantity}}
</span>
</mat-cell>
</ng-container>
<!-- Active Column -->
<ng-container cdkColumnDef="active">
<mat-header-cell *cdkHeaderCellDef mat-sort-header fxHide fxShow.gt-xs>Active</mat-header-cell>
<mat-cell *cdkCellDef="let product" fxHide fxShow.gt-xs>
<mat-icon *ngIf="product.active" class="active-icon mat-green-600-bg s-16">check</mat-icon>
<mat-icon *ngIf="!product.active" class="active-icon mat-red-500-bg s-16">close</mat-icon>
</mat-cell>
</ng-container>
<mat-header-row *cdkHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *cdkRowDef="let product; columns: displayedColumns;"
class="product"
matRipple
[routerLink]="'/apps/e-commerce/products/'+product.id+'/'+product.handle">
</mat-row>
</mat-table>
<mat-paginator #paginator
[length]="dataSource.filteredData.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 25, 100]">
</mat-paginator>
</div>
<!-- / CONTENT CARD -->
</div>
<!-- / CENTER -->
</div>

View File

@ -0,0 +1,80 @@
@import "src/app/core/scss/fuse";
:host {
.header {
.search-input-wrapper {
max-width: 480px;
}
@include media-breakpoint-down(xs) {
height: 176px !important;
min-height: 176px !important;
max-height: 176px !important;
}
}
.top-bg {
@include media-breakpoint-down(xs) {
height: 240px;
}
}
.products-table {
flex: 1 1 auto;
border-bottom: 1px solid rgba(0, 0, 0, .12);
.mat-header-row {
min-height: 64px;
}
.product {
position: relative;
cursor: pointer;
height: 84px;
}
.mat-cell {
min-width: 0;
display: flex;
align-items: center;
}
.mat-column-id {
flex: 0 1 84px;
}
.mat-column-image {
flex: 0 1 84px;
.product-image {
width: 52px;
height: 52px;
border: 1px solid rgba(0, 0, 0, .12);
}
}
.mat-column-buttons {
flex: 0 1 80px;
}
.quantity-indicator {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
border-radius: 4px;
margin-right: 8px;
& + span {
display: inline-block;
vertical-align: middle;
}
}
.active-icon {
border-radius: 50%;
}
}
}

View File

@ -0,0 +1,165 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { EcommerceProductsService } from './products.service';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import { fuseAnimations } from '../../../../../core/animations';
import { MatPaginator, MatSort } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { FuseUtils } from '../../../../../core/fuseUtils';
@Component({
selector : 'fuse-e-commerce-products',
templateUrl: './products.component.html',
styleUrls : ['./products.component.scss'],
animations : fuseAnimations
})
export class FuseEcommerceProductsComponent implements OnInit
{
dataSource: FilesDataSource | null;
displayedColumns = ['id', 'image', 'name', 'category', 'price', 'quantity', 'active'];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild('filter') filter: ElementRef;
@ViewChild(MatSort) sort: MatSort;
constructor(
private productsService: EcommerceProductsService
)
{
}
ngOnInit()
{
this.dataSource = new FilesDataSource(this.productsService, this.paginator, this.sort);
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => {
if ( !this.dataSource )
{
return;
}
this.dataSource.filter = this.filter.nativeElement.value;
});
}
}
export class FilesDataSource extends DataSource<any>
{
_filterChange = new BehaviorSubject('');
_filteredDataChange = new BehaviorSubject('');
get filteredData(): any
{
return this._filteredDataChange.value;
}
set filteredData(value: any)
{
this._filteredDataChange.next(value);
}
get filter(): string
{
return this._filterChange.value;
}
set filter(filter: string)
{
this._filterChange.next(filter);
}
constructor(
private productsService: EcommerceProductsService,
private _paginator: MatPaginator,
private _sort: MatSort
)
{
super();
this.filteredData = this.productsService.products;
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<any[]>
{
const displayDataChanges = [
this.productsService.onProductsChanged,
this._paginator.page,
this._filterChange,
this._sort.sortChange
];
return Observable.merge(...displayDataChanges).map(() => {
let data = this.productsService.products.slice();
data = this.filterData(data);
this.filteredData = [...data];
data = this.sortData(data);
// Grab the page's slice of data.
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
return data.splice(startIndex, this._paginator.pageSize);
});
}
filterData(data)
{
if ( !this.filter )
{
return data;
}
return FuseUtils.filterArrayByString(data, this.filter);
}
sortData(data): any[]
{
if ( !this._sort.active || this._sort.direction === '' )
{
return data;
}
return data.sort((a, b) => {
let propertyA: number | string = '';
let propertyB: number | string = '';
switch ( this._sort.active )
{
case 'id':
[propertyA, propertyB] = [a.id, b.id];
break;
case 'name':
[propertyA, propertyB] = [a.name, b.name];
break;
case 'categories':
[propertyA, propertyB] = [a.categories[0], b.categories[0]];
break;
case 'price':
[propertyA, propertyB] = [a.priceTaxIncl, b.priceTaxIncl];
break;
case 'quantity':
[propertyA, propertyB] = [a.quantity, b.quantity];
break;
case 'active':
[propertyA, propertyB] = [a.active, b.active];
break;
}
const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
const valueB = isNaN(+propertyB) ? propertyB : +propertyB;
return (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1);
});
}
disconnect()
{
}
}

View File

@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class EcommerceProductsService implements Resolve<any>
{
products: any[];
onProductsChanged: BehaviorSubject<any> = new BehaviorSubject({});
constructor(
private http: HttpClient
)
{
}
/**
* Resolve
* @param {ActivatedRouteSnapshot} route
* @param {RouterStateSnapshot} state
* @returns {Observable<any> | Promise<any> | any}
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any
{
return new Promise((resolve, reject) => {
Promise.all([
this.getProducts()
]).then(
() => {
resolve();
},
reject
);
});
}
getProducts(): Promise<any>
{
return new Promise((resolve, reject) => {
this.http.get('api/e-commerce-products')
.subscribe((response: any) => {
this.products = response;
this.onProductsChanged.next(this.products);
resolve(response);
}, reject);
});
}
}

View File

@ -32,6 +32,32 @@ export class NavigationModel
'icon' : 'today', 'icon' : 'today',
'url' : '/apps/calendar' 'url' : '/apps/calendar'
}, },
{
'id' : 'e-commerce',
'title' : 'E-Commerce',
'type' : 'collapse',
'icon' : 'shopping_cart',
'children': [
{
'id' : 'dashboard',
'title': 'Dashboard',
'type' : 'item',
'url' : '/apps/e-commerce/dashboard'
},
{
'id' : 'dashboard',
'title': 'Products',
'type' : 'item',
'url' : '/apps/e-commerce/products'
},
{
'id' : 'dashboard',
'title': 'Orders',
'type' : 'item',
'url' : '/apps/e-commerce/orders'
}
]
},
{ {
'id' : 'mail', 'id' : 'mail',
'title': 'Mail', 'title': 'Mail',
@ -800,3 +826,4 @@ export class NavigationModel
]; ];
} }
} }