diff --git a/README.md b/README.md index 9252bea4..1897d10d 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,191 @@ # Angular Első Csapatmunka ## Feladat + Készítsetek egy Webshopot Angular keretrendszer segítségével. ## Előkészítés + - A csapat egyik tagja forkolja a következő repo -t: - `https://github.com/cherryApp/str-angular-project-001` - Hívja meg a többieket együttműködőknek a github felületén. - Mindenki klónozza le magának a forkolt repo -t. -- Üzembehelyezés a szokásos: +- Üzembehelyezés a szokásos: - `cd str-angular-project-001` - `npm i` - `code . -r` - `npm start` ## Csapatmunka + - Válasszátok ki az oldal témáját (sícuccok, főzés, könyvek, filmek, stb...). - Keressétek meg a hozzávalókat: képek, egyéb adatok. - Egyenlő arányban osszátok el a feladatokat. - Minden komponensen csak egy ember dolgozzon. - Beszéljétek meg a stílust. -- Osszátok ki, ki melyik feladatot végzi el: el kell készíteni az osztályokat, -majd három komponenst kell készíteni. Ez 4 jól elkülönülő faladat, időrendben. -- Ha megvan minden hozzávaló, akkor össze kell állítani az oldalt a szükséges -komponensekből. Ezt érdemes együtt csinálni. -- Miután minden megfelelően működik, csak akkor fogjatok hozzá a stílusok -testreszabásának. Amit csak lehet, a komponensek saját stíluslapjába írjatok. +- Osszátok ki, ki melyik feladatot végzi el: el kell készíteni az osztályokat, + majd három komponenst kell készíteni. Ez 4 jól elkülönülő faladat, időrendben. +- Ha megvan minden hozzávaló, akkor össze kell állítani az oldalt a szükséges + komponensekből. Ezt érdemes együtt csinálni. +- Miután minden megfelelően működik, csak akkor fogjatok hozzá a stílusok + testreszabásának. Amit csak lehet, a komponensek saját stíluslapjába írjatok. - A Git közös használatáról konzultálunk a projekt indító megbeszélésen. ## Alap osztályok -Mielőtt hozzákezdenétek a komponensek generálásához, készítsétek el a + +Mielőtt hozzákezdenétek a komponensek generálásához, készítsétek el a szükséges oszályokat és szolgáltatásokat (service). + - Product osztály: id, catId, name, description, image, price, stock, featured, active - Category osztály: id, name, description -> _Ezeket a model almappába érdemes elhelyezni az app mappán belül, hogy egy helyen legyenek._ -- ProductService: ez egy service legyen, így tudtok egyet generálni a service almappába: -`ng g service service/kutya` -> A ProductService tartalmazzon egy list nevű változót, ami a termékek tömbje legyen. -> Ebben szerepeljen random adatokkal legalább 50 termék, segítség: `https://mockaroo.com` + > _Ezeket a model almappába érdemes elhelyezni az app mappán belül, hogy egy helyen legyenek._ +- ProductService: ez egy service legyen, így tudtok egyet generálni a service almappába: + `ng g service service/kutya` + > A ProductService tartalmazzon egy list nevű változót, ami a termékek tömbje legyen. + > Ebben szerepeljen random adatokkal legalább 50 termék, segítség: `https://mockaroo.com` ## Home component + Amint látjátok, a fő komponenseket már elkészítettem és a menüt is beállítottam. + - Ez a főoldala a webshopnak. -- Jelenjen meg egy kiemelt termék sor az oldal tetején. -> 5 kiemelt terméket tartalmazzon, azok közül jelenjenek meg, amelyek featured tulajdonsága true. -- Jelenjen meg egy akciós termék sor a kiemelt termékek alatt, ide bármilyen random termék -kerülhet, szintén 5 darab legyen. +- Jelenjen meg egy kiemelt termék sor az oldal tetején. + > 5 kiemelt terméket tartalmazzon, azok közül jelenjenek meg, amelyek featured tulajdonsága true. +- Jelenjen meg egy akciós termék sor a kiemelt termékek alatt, ide bármilyen random termék + kerülhet, szintén 5 darab legyen. ## Category oldalak + A kategória oldalak linkjét a ConfigService -ben tudjátok átnevezni. -- Jelenjen meg itt is a kiemelt termék sor 5 termékkel, de ezeknek már ebből a kategóriából kell jönnie. -Ezek azok a termékek amelyek az adott kategóriához tartoznak és a featured tulajdonságuk true. -- Jelenjen meg alatta a terméklista. Itt az összes termék látható legyen rácsrendszerben, amely -az adott kategóriában található. Azt, hogy melyik termék melyik kategóriában van, a catId mondja meg. -- Legyen a terméklista tetején egy szűrőmező. Ez egy input mező, ha gépelni kezdenek bele, akkor -a név alapján kezdje el szűrni a termékeket, a legutóbbi feladathoz hasonlóan. + +- Jelenjen meg itt is a kiemelt termék sor 5 termékkel, de ezeknek már ebből a kategóriából kell jönnie. + Ezek azok a termékek amelyek az adott kategóriához tartoznak és a featured tulajdonságuk true. +- Jelenjen meg alatta a terméklista. Itt az összes termék látható legyen rácsrendszerben, amely + az adott kategóriában található. Azt, hogy melyik termék melyik kategóriában van, a catId mondja meg. +- Legyen a terméklista tetején egy szűrőmező. Ez egy input mező, ha gépelni kezdenek bele, akkor + a név alapján kezdje el szűrni a termékeket, a legutóbbi feladathoz hasonlóan. ## Szükséges komponensek + Ezeket nektek kell legenerálni, például így: `ng g c common/product-card` ### 1. Termékkártya komponens + Ez jeleníti meg az egyes termékeket. Az alábbi részei vannak: + - Termékkép, teljes szélességében a kártya felső 30 - 50% át foglalja el. -- Alatta egymás alatt: név, rövid leírás, készlet, ár. A formázás rátok van bízva. -Kiindulásnak jó lehet (de módosítsátok): `https://getbootstrap.com/docs/4.6/components/card/#example` -- Működése: input tulajdonságként kapja meg a megjelenített terméket, -pld: `` +- Alatta egymás alatt: név, rövid leírás, készlet, ár. A formázás rátok van bízva. + Kiindulásnak jó lehet (de módosítsátok): `https://getbootstrap.com/docs/4.6/components/card/#example` +- Működése: input tulajdonságként kapja meg a megjelenített terméket, + pld: `` - Statikus komponens, azaz nem kell bele output tulajdonság (egyenlőre :)))) ### 2. Temék lapozó komponens -Az öt terméket jeleníti meg. A főoldalon kettő, a kategória oldalakon egy van belőle, -csak különböző termékekkel. Tehát ez jeleníti meg a főoldalon a kiemelt és akciós + +Az öt terméket jeleníti meg. A főoldalon kettő, a kategória oldalakon egy van belőle, +csak különböző termékekkel. Tehát ez jeleníti meg a főoldalon a kiemelt és akciós termékeket, a kategória oldalakon pedig a kategória kiemelt termékeit. -- Működése: input tulajdonságként kapja meg az 5 terméket. *ngFor használatával -jeleníti meg őket, minden egy termék egy product-card komponens lesz. -- Extra: ha valaki nagyon unatkozik, akkor több termék is lehet benne, jobbra - balra -lapozó gombokkal. + +- Működése: input tulajdonságként kapja meg az 5 terméket. \*ngFor használatával + jeleníti meg őket, minden egy termék egy product-card komponens lesz. +- Extra: ha valaki nagyon unatkozik, akkor több termék is lehet benne, jobbra - balra + lapozó gombokkal. ### 3. Termék lista komponens -Ez a komponens hasonlóan működik mint a termék lapozó, de ez korlátlan számú terméket + +Ez a komponens hasonlóan működik mint a termék lapozó, de ez korlátlan számú terméket képes megjeleníteni. -- Működése: input tulajdonságként kapja meg a termékek tömbjét. Ezeket bejárva (*ngFor) -minden terméket egy product-card komponenssel jelenít meg. -- Szűrés: generáljatok egy filter pipe -ot és azt alkalmazzátok a szűrésre. -> Legyen egy input mező a termékek felett (de ezen a komponensen belül). -> Amikor módosítják a taralmát, akkor módosuljon egy változó, pld: `phrase`. -> Ez a változó legyen átadva a filternek, ami ez alapján szűrje le a -kapott termékek tömbjét. Ezt ott tudjátok alkalmazni, ahol az *ngFor -bejárja a termékeket, pld: + +- Működése: input tulajdonságként kapja meg a termékek tömbjét. Ezeket bejárva (\*ngFor) + minden terméket egy product-card komponenssel jelenít meg. +- Szűrés: generáljatok egy filter pipe -ot és azt alkalmazzátok a szűrésre. + > Legyen egy input mező a termékek felett (de ezen a komponensen belül). + > Amikor módosítják a taralmát, akkor módosuljon egy változó, pld: `phrase`. + > Ez a változó legyen átadva a filternek, ami ez alapján szűrje le a + > kapott termékek tömbjét. Ezt ott tudjátok alkalmazni, ahol az \*ngFor + > bejárja a termékeket, pld: + ```html - + ``` + > Működő példa: `https://github.com/Training360/str-angular-002/tree/main/angular-components-pipes` ## Megjelenés -A Bootstrap keretrendszert és a FontAwesom ikonokat előre telepítettem és beállítottam. -- Írjatok egyéni scss állományokat igény szerint, alakítsátok a színeket és a megjelenést -a saját ízlésetek alapján. -- RESZPONZIVITÁS: mobil nézetben 1, tablet nézetben 2 és asztali nézetben 5 termék -jelenjen meg egymás mellett. A menüt már megoldottam. -- Termékek képei: a képeket az `app/assets` mappába helyezzétek el. Ez egy -speciális mappa, a webpack átmásolja a kész alkalmazásba. Ha mondjuk az -ebben a mappában létrehozol egy `img` almappát a képeknek, akkor egy képet így -érsz el: `/assets/img/logo.png`. A képeket érdemes egyforma méretűre vágni, hogy -ne kelljen vele annyit dolgozni a stílusnál. + +A Bootstrap keretrendszert és a FontAwesom ikonokat előre telepítettem és beállítottam. + +- Írjatok egyéni scss állományokat igény szerint, alakítsátok a színeket és a megjelenést + a saját ízlésetek alapján. +- RESZPONZIVITÁS: mobil nézetben 1, tablet nézetben 2 és asztali nézetben 5 termék + jelenjen meg egymás mellett. A menüt már megoldottam. +- Termékek képei: a képeket az `app/assets` mappába helyezzétek el. Ez egy + speciális mappa, a webpack átmásolja a kész alkalmazásba. Ha mondjuk az + ebben a mappában létrehozol egy `img` almappát a képeknek, akkor egy képet így + érsz el: `/assets/img/logo.png`. A képeket érdemes egyforma méretűre vágni, hogy + ne kelljen vele annyit dolgozni a stílusnál. ## Extra funkciók + - A lista nézetben lehessen a termékeket szűrni nem csak név, hanem más paraméterek alapján is. - A lista nézetben lehessen rendezni is a termék kártyákat, mondjuk ár, név, akció, kiemelt alapján. ## Sok sikert! +--- + +# Második Felvonás + +> A második körben a már elkészült alkalmazást kell továbbfejleszteni. A feladat +> egy adminisztrációs felület készítése, ahol a termékeket szerkeszteni lehet. Ez +> az admin felület a termékeket már élő szerverről fogja betölteni. + +## AdminComponent + +- Hozzatok létre egy új komponenst, admin néven. +- Állítsátok be, hogy ez megjelenjen a menüben és az `admin` url -en legyen + elérhető. +- Ezen az oldalon fog megjelenni egy szerkesztő komponens, ahol a termékek + adatait lehet módosítani. + +## DataEditorComponent + +- Hozzatok létre egy új komponenst, a neve data-editor legyen. Ezt a komponenst + helyezzétek el az admin oldalon. +- Jelenjen meg benne egy adat lista, táblázatos formában (ez lehet valóban egy + táblázat, vagy ahhoz hasonlóan egy div -ekből vagy komponensekből álló lista). +- Ez a komponenst a termékek adatok szerkesztését látja majd el. +- Az adatsorokban az egyes adatok input elemként jelenjenek meg, kétirányú + adatkötéssel. Minden adatsor végén legyen három gomb a fő műveletekhez. Az `id` + tulajdonságot ne lehessen szerkeszteni. +- Az adatlistát lehessen szűrni egy bizonyos oszlop alapján egy beviteli + mezőbe írva. + +## Json server + +- Költöztessétek át a termékek adatait a ProductService -ből egy .json fájlba és + a tanultak szerint indítsatok egy json-server -t, ami kiszolgálja a termék + adatokat és lehetővé teszi a termékek szerkesztését. + [json-server dokumentáció](https://github.com/typicode/json-server) + +## ProductService + +- Az alkalmazás már rendelkezik a ProductService szolgáltatással, ezt kell + továbbfejleszteni. +- Készítsetek metódusokat, amelyek lekérik az összes terméket, frissítenek, + törölnek bizonyos termékeket az adatbázisból. (getAll, update, remove). +- Ezek mind a json-server szabványos kéréseit használják az adatok valós + frissítésére. +- A getAll esetén nem kell paraméter, mivel itt az összes terméket le akarjuk + kérni, de az update és remove metódusoknak át kell adni az adott terméket, hogy + az id alapján meg tudjuk határoni, hogy a szerver melyiket frissítse vagy + törölje. + +## Extrák + +Az admin oldalon a táblázat alatt vagy felett legyen egy lapozó, és ne az összes termék jelenjen meg egyszerre, csak egy oldalnyi. +A szűrés az admin oldalon ne csak névre működjön, hanem a termékek többi adatára is lehessen szűrni. +Az admin oldalon a táblázatban az adatokat lehessen az összes oszlop szerint rendezni. (elég csak növekvő sorrendben, de plusz pont, ha lehet az irányt is változtatni) +Az admin oldalon lehessen új termékeket is felvenni az adatbázisba. + +## Sok sikert! diff --git a/angular.json b/angular.json index c64b44e4..f78be51c 100644 --- a/angular.json +++ b/angular.json @@ -128,6 +128,10 @@ } } } - }}, - "defaultProject": "str-angular-project001" + } + }, + "defaultProject": "str-angular-project001", + "cli": { + "analytics": "d376a670-dafa-4ee9-8788-4b056193a218" + } } diff --git a/server/products.json b/server/products.json new file mode 100644 index 00000000..d4d00817 --- /dev/null +++ b/server/products.json @@ -0,0 +1,554 @@ +{ + "products": [ + { + "id": 1, + "catId": "cat01", + "name": "Ferrari", + "description": "599 GTB Fiorano", + "image": "../../assets/img/01-Ferrari-599.jpg", + "price": 4049.34, + "stock": 6, + "featured": true, + "active": false + }, + { + "id": 2, + "catId": "cat01", + "name": "Mercedes-Benz", + "description": "CL-Class", + "image": "../../assets/img/02-Mercedes-Benz-CL-Class.jpg", + "price": 5914.03, + "stock": 4, + "featured": true, + "active": false + }, + { + "id": 3, + "catId": "cat01", + "name": "Pontiac", + "description": "1000", + "image": "../../assets/img/03-Pontiac-1000.jpg", + "price": 4002.02, + "stock": 1, + "featured": true, + "active": true + }, + { + "id": 4, + "catId": "cat01", + "name": "Mercedes-Benz", + "description": "M-Class", + "image": "../../assets/img/04-Mercedes-Benz-M-Class.jpg", + "price": 5183.87, + "stock": 1, + "featured": false, + "active": true + }, + { + "id": 5, + "catId": "cat01", + "name": "Subaru", + "description": "Legacy", + "image": "../../assets/img/05-Subaru-Legacy.jpg", + "price": 5780.13, + "stock": 2, + "featured": false, + "active": false + }, + { + "id": 6, + "catId": "cat01", + "name": "Toyota", + "description": "Celica", + "image": "../../assets/img/06-Toyota-Celica.jpg", + "price": 8312.35, + "stock": 1, + "featured": true, + "active": false + }, + { + "id": 7, + "catId": "cat01", + "name": "Ford", + "description": "Fusion", + "image": "../../assets/img/07-Ford-Fusion.jpg", + "price": 4565.55, + "stock": 4, + "featured": true, + "active": true + }, + { + "id": 8, + "catId": "cat01", + "name": "Ford", + "description": "Bronco", + "image": "../../assets/img/08-Ford-Bornco.jpg", + "price": 5870.24, + "stock": 2, + "featured": true, + "active": false + }, + { + "id": 9, + "catId": "cat01", + "name": "Ford", + "description": "Escort", + "image": "../../assets/img/09-Ford-Escort.jpg", + "price": 4326.4, + "stock": 4, + "featured": true, + "active": false + }, + { + "id": 10, + "catId": "cat01", + "name": "Ford", + "description": "F-Series", + "image": "../../assets/img/10-Ford-F-Series.jpg", + "price": 5892.99, + "stock": 3, + "featured": true, + "active": false + }, + { + "id": 11, + "catId": "cat01", + "name": "Lexus", + "description": "RX Hybrid", + "image": "../../assets/img/11-Lexus-RX-Hybrid.jpg", + "price": 5639.32, + "stock": 1, + "featured": true, + "active": true + }, + { + "id": 12, + "catId": "cat01", + "name": "Land Rover", + "description": "Defender 110", + "image": "../../assets/img/12-Land-Rover-Defender-110.jpg", + "price": 4306.9, + "stock": 1, + "featured": false, + "active": true + }, + { + "id": 13, + "catId": "cat01", + "name": "Ford", + "description": "F250", + "image": "../../assets/img/13-Ford_F250.jpg", + "price": 7468.42, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 14, + "catId": "cat01", + "name": "Ford", + "description": "EXP", + "image": "../../assets/img/14-Ford-EXP.jpg", + "price": 7164.26, + "stock": 1, + "featured": true, + "active": true + }, + { + "id": 15, + "catId": "cat01", + "name": "Acura", + "description": "Vigor", + "image": "../../assets/img/15-Acura-Vigor.jpg", + "price": 4495.51, + "stock": 3, + "featured": true, + "active": true + }, + { + "id": 16, + "catId": "cat01", + "name": "Mitsubishi", + "description": "Galant", + "image": "../../assets/img/16-Mitsubishi-Galant.jpg", + "price": 5417.73, + "stock": 2, + "featured": false, + "active": false + }, + { + "id": 17, + "catId": "cat01", + "name": "Hyundai", + "description": "Sonata", + "image": "../../assets/img/17-Hyundai-Sonata.jpg", + "price": 7977.57, + "stock": 5, + "featured": false, + "active": false + }, + { + "id": 18, + "catId": "cat01", + "name": "Toyota", + "description": "Sienna", + "image": "../../assets/img/18-Toyota-Sienna.jpg", + "price": 5430.52, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 19, + "catId": "cat01", + "name": "Mercury", + "description": "Topaz", + "image": "../../assets/img/19-Mercury-Topaz.jpg", + "price": 4325.64, + "stock": 3, + "featured": false, + "active": false + }, + { + "id": 20, + "catId": "cat01", + "name": "GMC", + "description": "Savana", + "image": "../../assets/img/20-GMC-Savana.jpg", + "price": 6295.92, + "stock": 3, + "featured": true, + "active": false + }, + { + "id": 21, + "catId": "cat01", + "name": "Bentley", + "description": "Continental", + "image": "../../assets/img/21-Bentley-Continental.jpg", + "price": 8646.79, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 22, + "catId": "cat01", + "name": "Toyota", + "description": "Xtra", + "image": "../../assets/img/22-Toyota-Xtra.jpg", + "price": 6024.99, + "stock": 5, + "featured": true, + "active": true + }, + { + "id": 23, + "catId": "cat01", + "name": "Chevrolet", + "description": "HHR", + "image": "../../assets/img/23-Chevrolet-HRR.jpg", + "price": 5849.04, + "stock": 4, + "featured": true, + "active": true + }, + { + "id": 24, + "catId": "cat01", + "name": "Lincoln", + "description": "Mark LT", + "image": "../../assets/img/24-Lincoln-Mark-LT.jpg", + "price": 7557.37, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 25, + "catId": "cat01", + "name": "Kia", + "description": "Rondo", + "image": "../../assets/img/25-Kia-Rondo.jpg", + "price": 7328.99, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 26, + "catId": "cat02", + "name": "Porsche", + "description": "Boxster", + "image": "../../assets/img/26-2020_porsche_718-boxster.jpg", + "price": 5178.15, + "stock": 2, + "featured": true, + "active": true + }, + { + "id": 27, + "catId": "cat02", + "name": "GMC", + "description": "Sonoma Club", + "image": "../../assets/img/27-gmc-sonoma.jpg", + "price": 7899.65, + "stock": 1, + "featured": true, + "active": true + }, + { + "id": 28, + "catId": "cat02", + "name": "Mercury", + "description": "Sable", + "image": "../../assets/img/28-mercury-sable.jpg", + "price": 8405.19, + "stock": 2, + "featured": false, + "active": false + }, + { + "id": 29, + "catId": "cat02", + "name": "Lotus", + "description": "Elise", + "image": "../../assets/img/29-lotus-elisejpg.jpg", + "price": 4101.31, + "stock": 6, + "featured": false, + "active": false + }, + { + "id": 30, + "catId": "cat02", + "name": "Infiniti", + "description": "QX56", + "image": "../../assets/img/30-01-2013-infiniti-qx56.jpg", + "price": 5769.75, + "stock": 6, + "featured": true, + "active": true + }, + { + "id": 31, + "catId": "cat02", + "name": "Chevrolet", + "description": "Lumina", + "image": "../../assets/img/31-chevrolet-lumina.jpg", + "price": 6098.48, + "stock": 5, + "featured": true, + "active": true + }, + { + "id": 32, + "catId": "cat02", + "name": "Dodge", + "description": "Magnum", + "image": "../../assets/img/32-dodge-magnum.jpg", + "price": 7304.7, + "stock": 5, + "featured": false, + "active": false + }, + { + "id": 33, + "catId": "cat02", + "name": "Mazda", + "description": "Familia", + "image": "../../assets/img/33-mazda-familia.jpg", + "price": 8491.2, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 34, + "catId": "cat02", + "name": "GMC", + "description": "Yukon XL 1500", + "image": "../../assets/img/34-gmc-yukon.jpg", + "price": 5875.37, + "stock": 6, + "featured": true, + "active": true + }, + { + "id": 35, + "catId": "cat02", + "name": "Saab", + "description": "9-3", + "image": "../../assets/img/35-saab-9-3-2002-kulso-1.jpg", + "price": 5200.86, + "stock": 1, + "featured": false, + "active": true + }, + { + "id": 36, + "catId": "cat02", + "name": "Volkswagen", + "description": "Jetta III", + "image": "../../assets/img/36-vw-jetta-iii.jpg", + "price": 7336.35, + "stock": 5, + "featured": true, + "active": true + }, + { + "id": 37, + "catId": "cat02", + "name": "Toyota", + "description": "Land Cruiser", + "image": "../../assets/img/37-2021-toyota-land-cruiser-mmp-1-1596116800.jpg", + "price": 7622.1, + "stock": 1, + "featured": false, + "active": false + }, + { + "id": 38, + "catId": "cat02", + "name": "Ford", + "description": "E-Series", + "image": "../../assets/img/38-2010_ford_econoline_cargo_van.jpg", + "price": 4761.38, + "stock": 3, + "featured": true, + "active": true + }, + { + "id": 39, + "catId": "cat02", + "name": "Ford", + "description": "Taurus", + "image": "../../assets/img/39-2010_Ford_Taurus.jpg", + "price": 7736.82, + "stock": 5, + "featured": true, + "active": false + }, + { + "id": 40, + "catId": "cat02", + "name": "Subaru", + "description": "SVX", + "image": "../../assets/img/40-subaru-svx.jpg", + "price": 5128.02, + "stock": 4, + "featured": false, + "active": true + }, + { + "id": 41, + "catId": "cat02", + "name": "Audi", + "description": "Coupe GT", + "image": "../../assets/img/41-1986_audi_coupe.jpg", + "price": 6877.91, + "stock": 5, + "featured": false, + "active": false + }, + { + "id": 42, + "catId": "cat02", + "name": "Scion", + "description": "tC", + "image": "../../assets/img/42-scion-tc.jpg", + "price": 7160.94, + "stock": 3, + "featured": false, + "active": true + }, + { + "id": 43, + "catId": "cat02", + "name": "Lamborghini", + "description": "Gallardo", + "image": "../../assets/img/43-lamborghini-gallardo-lp-570-4-squadra-corse.jpg", + "price": 7937.14, + "stock": 5, + "featured": false, + "active": false + }, + { + "id": 44, + "catId": "cat02", + "name": "Chrysler", + "description": "Town & Country", + "image": "../../assets/img/44-2016_chrysler_town-and-country_passenger-minivan_limited_fq_oem_1_1600.jpg", + "price": 7077.4, + "stock": 4, + "featured": false, + "active": false + }, + { + "id": 45, + "catId": "cat02", + "name": "Jeep", + "description": "Wrangler", + "image": "../../assets/img/45-jeep-wrangler-rubicon-392-concept.jpg", + "price": 5639.57, + "stock": 3, + "featured": true, + "active": false + }, + { + "id": 46, + "catId": "cat02", + "name": "Lincoln", + "description": "Town Car", + "image": "../../assets/img/46-lincoln-town-car.jpg", + "price": 5529.73, + "stock": 3, + "featured": true, + "active": true + }, + { + "id": 47, + "catId": "cat02", + "name": "Toyota", + "description": "Celica", + "image": "../../assets/img/47-toyota-celica-1.jpg", + "price": 5769.48, + "stock": 3, + "featured": true, + "active": false + }, + { + "id": 48, + "catId": "cat02", + "name": "Ford", + "description": "Windstar", + "image": "../../assets/img/48-Ford_Windstar.jpg", + "price": 8997.02, + "stock": 4, + "featured": false, + "active": true + }, + { + "id": 49, + "catId": "cat02", + "name": "Subaru", + "description": "Outback", + "image": "../../assets/img/49-2020_subaru_outback_4dr-suv_onyx-edition-xt_fq_oem_1_1600.jpg", + "price": 4533.05, + "stock": 1, + "featured": true, + "active": true + }, + { + "id": 50, + "catId": "cat02", + "name": "Chevrolet", + "description": "Cavalier", + "image": "../../assets/img/50-chevrolet-cavalier.jpg", + "price": 5104.65, + "stock": 1, + "featured": false, + "active": false + } + ] +} \ No newline at end of file diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index ee8e058b..fe9045f9 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -2,7 +2,9 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { Cat01Component } from './page/cat01/cat01.component'; import { Cat02Component } from './page/cat02/cat02.component'; +import { Cat03Component } from './page/cat03/cat03.component'; import { HomeComponent } from './page/home/home.component'; +import { AdminComponent } from './page/admin/admin.component'; const routes: Routes = [ { @@ -17,6 +19,14 @@ const routes: Routes = [ path: 'cat02', component: Cat02Component, }, + { + path: 'cat03', + component: Cat03Component, + }, + { + path: 'admin', + component: AdminComponent, + }, { path: '**', component: HomeComponent, diff --git a/src/app/app.component.html b/src/app/app.component.html index a47bd86d..82c2052f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,8 +1,13 @@ - -
-
-
+ + + + + +
+
+
-
+ + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29b..b8b51678 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,7 @@ +@use '../assets/modules/colors'as *; + +body { + background-color: $background-color; + height: 100%; + width: 100vw; +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 007f4157..8857c715 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { ProductService } from './service/product.service'; @Component({ selector: 'app-root', @@ -7,4 +8,15 @@ import { Component } from '@angular/core'; }) export class AppComponent { title = 'str-angular-project001'; + + constructor( + private pservice: ProductService + ) { + this.pservice.getAll().forEach( + value => { + console.log("All Product: " , value); + } + ) + } } + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1bf5881f..e5a29a1f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -7,6 +8,18 @@ import { NavigationComponent } from './navigation/navigation.component'; import { Cat01Component } from './page/cat01/cat01.component'; import { Cat02Component } from './page/cat02/cat02.component'; import { HomeComponent } from './page/home/home.component'; +import { CardsComponent } from './common/cards/cards.component'; +import { Cat03Component } from './page/cat03/cat03.component'; +import { ProductfilterPipe } from './pipe/productfilter.pipe'; +import { ProductsortPipe } from './pipe/productsort.pipe'; +import { AdminComponent } from './page/admin/admin.component'; +import { DataEditorComponent } from './common/data-editor/data-editor.component'; +import { FormsModule } from '@angular/forms'; +import { ActionButtonComponent } from './common/action-button/action-button.component'; +import { ActionButtonGroupComponent } from './common/action-button-group/action-button-group.component'; +import { IconComponent } from './common/icon/icon.component'; +import { ProductService } from './service/product.service'; +import { CategoryfilterPipe } from './pipe/categoryfilter.pipe'; @NgModule({ declarations: [ @@ -14,13 +27,25 @@ import { HomeComponent } from './page/home/home.component'; NavigationComponent, Cat01Component, Cat02Component, - HomeComponent + HomeComponent, + CardsComponent, + Cat03Component, + ProductfilterPipe, + ProductsortPipe, + AdminComponent, + DataEditorComponent, + ActionButtonComponent, + ActionButtonGroupComponent, + IconComponent, + CategoryfilterPipe, ], imports: [ BrowserModule, - AppRoutingModule + AppRoutingModule, + FormsModule, + HttpClientModule, ], - providers: [], + providers: [ProductService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/common/action-button-group/action-button-group.component.html b/src/app/common/action-button-group/action-button-group.component.html new file mode 100644 index 00000000..2d125843 --- /dev/null +++ b/src/app/common/action-button-group/action-button-group.component.html @@ -0,0 +1,8 @@ +
+ + + + + + +
diff --git a/src/app/common/action-button-group/action-button-group.component.scss b/src/app/common/action-button-group/action-button-group.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/common/action-button-group/action-button-group.component.spec.ts b/src/app/common/action-button-group/action-button-group.component.spec.ts new file mode 100644 index 00000000..d6f0cb32 --- /dev/null +++ b/src/app/common/action-button-group/action-button-group.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ActionButtonGroupComponent } from './action-button-group.component'; + +describe('ActionButtonGroupComponent', () => { + let component: ActionButtonGroupComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ActionButtonGroupComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ActionButtonGroupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/action-button-group/action-button-group.component.ts b/src/app/common/action-button-group/action-button-group.component.ts new file mode 100644 index 00000000..5cfa1bfd --- /dev/null +++ b/src/app/common/action-button-group/action-button-group.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-action-button-group', + templateUrl: './action-button-group.component.html', + styleUrls: ['./action-button-group.component.scss'] +}) +export class ActionButtonGroupComponent implements OnInit { + + @Output() selectClick: EventEmitter = new EventEmitter(); + @Output() updateClick: EventEmitter = new EventEmitter(); + @Output() deleteClick: EventEmitter = new EventEmitter(); + + constructor() { } + + ngOnInit(): void { + } + + onSelectButtonClick(): void { + this.selectClick.emit(true); + } + + onUpdateButtonClick(): void { + this.updateClick.emit(true); + } + + onDeleteButtonClick(): void { + this.deleteClick.emit(true); + } + +} diff --git a/src/app/common/action-button/action-button.component.html b/src/app/common/action-button/action-button.component.html new file mode 100644 index 00000000..0e01b17c --- /dev/null +++ b/src/app/common/action-button/action-button.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/common/action-button/action-button.component.scss b/src/app/common/action-button/action-button.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/common/action-button/action-button.component.spec.ts b/src/app/common/action-button/action-button.component.spec.ts new file mode 100644 index 00000000..9d1e566c --- /dev/null +++ b/src/app/common/action-button/action-button.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ActionButtonComponent } from './action-button.component'; + +describe('ActionButtonComponent', () => { + let component: ActionButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ActionButtonComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ActionButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/action-button/action-button.component.ts b/src/app/common/action-button/action-button.component.ts new file mode 100644 index 00000000..ebd4232b --- /dev/null +++ b/src/app/common/action-button/action-button.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-action-button', + templateUrl: './action-button.component.html', + styleUrls: ['./action-button.component.scss'] +}) +export class ActionButtonComponent implements OnInit { + + @Input() icon: string = ''; + @Input() text: string = ''; + @Input() btnClass: string = ''; + + @Output() clicked: EventEmitter = new EventEmitter(); + + constructor() { } + + ngOnInit(): void { + } + + onUserClicked(): void { + this.clicked.emit(true); + } + + +} diff --git a/src/app/common/cards/cards.component.html b/src/app/common/cards/cards.component.html new file mode 100644 index 00000000..cc65f5e0 --- /dev/null +++ b/src/app/common/cards/cards.component.html @@ -0,0 +1,43 @@ +
+
+

The Godfather's Picks

+
+ + +
+

Explore our latest cars

+
+ +
diff --git a/src/app/common/cards/cards.component.scss b/src/app/common/cards/cards.component.scss new file mode 100644 index 00000000..c692957d --- /dev/null +++ b/src/app/common/cards/cards.component.scss @@ -0,0 +1,58 @@ +@use '../../../assets/modules/colors'as *; + +@import url('https://fonts.googleapis.com/css2?family=Permanent+Marker&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap'); + +.card { + + font-family: 'Roboto', Arial, Helvetica, sans-serif; + transition: all .2s ease-in-out; + + &-title { + color: $clickme; + font-family: 'Permanent Marker', Arial, Helvetica, sans-serif; + text-shadow: 2px 2px 3px $secondary-color; + } + + .btn-detail { + background-color: $secondary-color; + color: $primary-color; + } + + .btn-detail:hover { + color: $clickme; + } + + &-img-top { + box-sizing: border-box; + border-radius: 13px; + padding: 0.5rem; + object-fit: cover; + width: 100%; + } + + @media (min-width: 580px) and (max-width: 980px) { + &-img-top { + height: 17vw; + } + } + + @media (min-width: 980px) { + &-img-top { + height: 11vw; + } + } + +} + +.card:hover { + opacity: 1; + transform: scale(1.1); +} + +h2 { + color: $clickme; + font-family: 'Roboto', Arial, Helvetica, sans-serif; + font-weight: 600; + text-shadow: 2px 2px 3px $secondary-color; +} diff --git a/src/app/common/cards/cards.component.spec.ts b/src/app/common/cards/cards.component.spec.ts new file mode 100644 index 00000000..779ffb2f --- /dev/null +++ b/src/app/common/cards/cards.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardsComponent } from './cards.component'; + +describe('CardsComponent', () => { + let component: CardsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CardsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CardsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/cards/cards.component.ts b/src/app/common/cards/cards.component.ts new file mode 100644 index 00000000..330bb9ad --- /dev/null +++ b/src/app/common/cards/cards.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { ProductService } from 'src/app/service/product.service'; +import { Product } from 'src/app/model/product'; +import { HttpClient } from '@angular/common/http'; + +@Component({ + selector: 'app-cards', + templateUrl: './cards.component.html', + styleUrls: ['./cards.component.scss'] +}) +export class CardsComponent extends ProductService implements OnInit { + + listFeatured: Product[] = this.list.filter(e => e.featured == true).sort(() => 0.5 - Math.random()); + listNonFeatured: Product[] = this.list.filter(e => e.featured == false).sort(() => 0.5 - Math.random()); + + constructor(http: HttpClient) { super(http) } + + ngOnInit(): void { + } + +} diff --git a/src/app/common/data-editor/data-editor.component.html b/src/app/common/data-editor/data-editor.component.html new file mode 100644 index 00000000..24e669a6 --- /dev/null +++ b/src/app/common/data-editor/data-editor.component.html @@ -0,0 +1,66 @@ +
+
+
+
+ +
+
+
+
+ + +
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + +
{{ col.text }}
+ + +
+ + + +
+ + +
+
+
+
+
diff --git a/src/app/common/data-editor/data-editor.component.scss b/src/app/common/data-editor/data-editor.component.scss new file mode 100644 index 00000000..9eefda7c --- /dev/null +++ b/src/app/common/data-editor/data-editor.component.scss @@ -0,0 +1,26 @@ +@use '../../../assets/modules/colors'as *; + +@import url('https://fonts.googleapis.com/css2?family=Permanent+Marker&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap'); + +.container { + width: 100vw; +} + +.table { + font-family: 'Roboto', Arial, Helvetica, sans-serif; +} + +.btn-search { + background-color: $clickme; + + &:hover { + color: $primary-color; + } +} + +.page-link { + color: $secondary-color; +} + + diff --git a/src/app/common/data-editor/data-editor.component.spec.ts b/src/app/common/data-editor/data-editor.component.spec.ts new file mode 100644 index 00000000..1de20fa8 --- /dev/null +++ b/src/app/common/data-editor/data-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataEditorComponent } from './data-editor.component'; + +describe('DataEditorComponent', () => { + let component: DataEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DataEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DataEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/data-editor/data-editor.component.ts b/src/app/common/data-editor/data-editor.component.ts new file mode 100644 index 00000000..1f7ae649 --- /dev/null +++ b/src/app/common/data-editor/data-editor.component.ts @@ -0,0 +1,95 @@ +import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core'; +import { ConfigService } from 'src/app/service/config.service'; +import { ITableCol } from 'src/app/service/config.service'; +import { Product } from '../../../../src/app/model/product'; +import { ProductService } from '../../../../src/app/service/product.service'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router' +import { tap } from 'rxjs/operators'; + +interface IPageBtn { + page: number; +} + +@Component({ + selector: 'app-data-editor', + templateUrl: './data-editor.component.html', + styleUrls: ['./data-editor.component.scss'] +}) +export class DataEditorComponent implements OnInit { + + cols: ITableCol[] = this.config.tableCols; + col: Product = new Product(); + + filterKey: string = ''; + filterKeys: string[] = Object.keys(new Product()); + + @Output() selectClick: EventEmitter = new EventEmitter(); + @Output() updateClick: EventEmitter = new EventEmitter(); + @Output() deleteClick: EventEmitter = new EventEmitter(); + + onSelectClicked(): void { + this.selectClick.emit(this.col); + } + + onUpdateClicked(): void { + this.updateClick.emit(this.col); + } + + onDeleteClicked(): void { + this.deleteClick.emit(this.col); + } + + constructor(private config: ConfigService, private productService: ProductService, private router:Router) { } + + // Feri verzió + productList$: Observable = this.productService.getAll().pipe( + tap( products => this.productsProperties.count = products.length ) + ); + onUpdate(product:any):void{this.productService.updateProduct(product).subscribe(e=>alert("Product refreshed!")) } + onDelete(product:any):void{this.productService.deleteProduct(product).subscribe(e=>alert("Product deleted!")); + this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.router.onSameUrlNavigation='reload'; this.router.navigate(['/admin'])} + + ngOnInit(): void { + } + + phrase: string = ''; + + searchEvent(event: Event): void { + this.phrase = (event.target as HTMLInputElement).value; + } + + productsProperties: {count: number} = { + count: 0, + }; + pageSize: number = 10; + pageStart: number = 1; + currentPage: number = 1; + get paginator(): IPageBtn[] { + const pages: IPageBtn[] = []; + for (let i = 0; i < this.productsProperties.count / this.pageSize && pages.length < 10; i++ ) { + const page = this.pageStart + i; + pages.push({page}); + } + return pages; + } + get pageSliceStart(): number { + const index = this.currentPage - 1; + return index === 0 ? 0 : (index * this.pageSize); + } + get pageSliceEnd(): number { + return this.pageSliceStart + this.pageSize; + } + + onPaginate(event: Event, btn: IPageBtn): void { + event.preventDefault(); + this.currentPage = btn.page; + this.pageStart = (btn.page - 5) < 1 ? 1 : (btn.page - 5); + } + + onStepPage(event: Event, step: number): void { + event.preventDefault(); + this.currentPage += step; + this.pageStart = (this.currentPage - 5) < 1 ? 1 : (this.currentPage - 5); + } +} diff --git a/src/app/common/icon/icon.component.html b/src/app/common/icon/icon.component.html new file mode 100644 index 00000000..232e6e9d --- /dev/null +++ b/src/app/common/icon/icon.component.html @@ -0,0 +1 @@ + diff --git a/src/app/common/icon/icon.component.scss b/src/app/common/icon/icon.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/common/icon/icon.component.spec.ts b/src/app/common/icon/icon.component.spec.ts new file mode 100644 index 00000000..1719eb06 --- /dev/null +++ b/src/app/common/icon/icon.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IconComponent } from './icon.component'; + +describe('IconComponent', () => { + let component: IconComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ IconComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/icon/icon.component.ts b/src/app/common/icon/icon.component.ts new file mode 100644 index 00000000..3aa0e094 --- /dev/null +++ b/src/app/common/icon/icon.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-icon', + templateUrl: './icon.component.html', + styleUrls: ['./icon.component.scss'] +}) +export class IconComponent implements OnInit { + + @Input() icon: string = ''; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/model/category.spec.ts b/src/app/model/category.spec.ts new file mode 100644 index 00000000..484f29ff --- /dev/null +++ b/src/app/model/category.spec.ts @@ -0,0 +1,7 @@ +import { Category } from './category'; + +describe('Category', () => { + it('should create an instance', () => { + expect(new Category()).toBeTruthy(); + }); +}); diff --git a/src/app/model/category.ts b/src/app/model/category.ts new file mode 100644 index 00000000..db0f92ab --- /dev/null +++ b/src/app/model/category.ts @@ -0,0 +1,5 @@ +export class Category { + id: number = 0; + name: string = ''; + description: string = ''; +} diff --git a/src/app/model/debug.log b/src/app/model/debug.log new file mode 100644 index 00000000..fa431de9 --- /dev/null +++ b/src/app/model/debug.log @@ -0,0 +1,2 @@ +[0206/152932.526:ERROR:registration_protocol_win.cc(102)] CreateFile: A rendszer nem tallja a megadott fjlt. (0x2) +[0207/085845.863:ERROR:registration_protocol_win.cc(102)] CreateFile: A rendszer nem tallja a megadott fjlt. (0x2) diff --git a/src/app/model/product.spec.ts b/src/app/model/product.spec.ts new file mode 100644 index 00000000..abf5b050 --- /dev/null +++ b/src/app/model/product.spec.ts @@ -0,0 +1,7 @@ +import { Product } from './product'; + +describe('Product', () => { + it('should create an instance', () => { + expect(new Product()).toBeTruthy(); + }); +}); diff --git a/src/app/model/product.ts b/src/app/model/product.ts new file mode 100644 index 00000000..97b10c48 --- /dev/null +++ b/src/app/model/product.ts @@ -0,0 +1,28 @@ +import { Category } from './category'; + +export class Product extends Category { + id: number = 0; + catId: string = ''; + name: string = ''; + description: string = ''; + image?: string = ''; + price: number = 0; + stock: number = 0; + featured: boolean = false; + active: boolean = true; + + constructor(properties?: Product) { + super(); + if (properties) { + this.id = properties.id || 0; + this.catId = properties.catId || ''; + this.name = properties.name || ''; + this.description = properties.description || ''; + this.image = properties.image || ''; + this.price = properties.price || 0; + this.stock = properties.stock || 0; + this.featured = properties.featured || false; + this.active = properties.active || true; + } + } +} diff --git a/src/app/navigation/navigation.component.html b/src/app/navigation/navigation.component.html index b1186a68..86c522f4 100644 --- a/src/app/navigation/navigation.component.html +++ b/src/app/navigation/navigation.component.html @@ -1,30 +1,14 @@ -