Ophelia: Difference between revisions
No edit summary |
|||
(2 intermediate revisions by one other user not shown) | |||
Line 304: | Line 304: | ||
=== WebOphelia === | === WebOphelia === | ||
* WebOphelia -> Properties -> Debug -> App URL: http://localhost:5000/ | |||
* WebOphelia -> Properties -> Debug -> App URL: http://localhost: | |||
* Run WebOphelia | * Run WebOphelia | ||
Line 311: | Line 310: | ||
* npm install | * npm install | ||
* npm run start | * npm run start | ||
** Hetkel otsitakse serverit aadressilt localhost:5000 | |||
=== Seeded === | === Seeded === | ||
Line 324: | Line 324: | ||
=== Admin === | === Admin === | ||
* Näeb kasutajaid ning saab anda või ära võtta neilt õigusi | * Näeb kasutajaid ning saab anda või ära võtta neilt õigusi | ||
== Klientrakenduse retsensioon meeskonnale MealPlannerSolo == | |||
Retsensioon meeskonnale [https://wiki.itcollege.ee/index.php/MealPlannerSolo MealPlanner Solo] | |||
MealPlanner klient on kirjutatud kasutatud WPF tehnoloogiat. Kui MealPlanner tööle panna avatakse avaleht, | |||
kus on võimalik sisse logida või endale kasutaja registreerida, tõsi küll kumbki nuppudest ei tööta. | |||
Järgmine aken kuvatakse 3 valikut, "Recipes", "Ingredients", "People" - mille kõrval on kõigil üks nupp. | |||
People nupp sõnastatud "Vaata", samas kui teised nupud hoiavad endas tavalit "Button" sõnastust. | |||
Samuti on segamini eesti ja inglise keel. Iga nupu peale klikkides avatakse eraldi vaade. | |||
"Recipes" nupule vajutades, avatakse uus aken, kus on tühi kast ja nuppu "Avalehele", kahjuks sinna kasti ei õnnestunud midagi tekitada, | |||
seega ei oska öelda, kuidas täpselt see kastike toimima peaks. | |||
"Ingredients" nupule vajutades avatakse jälle uus aken, kus on tühi kast ja 3 input välja (Name, Unit, Allergen) ja "Lisa" nupp, | |||
jällegi eesti ja inglise keel on segamine. Kui täita väljad ja vajutada nuppu "Lisa", siis natukese ajapärast aken sulgub | |||
ja kuigi API töötab tagataustal, ei ole näha et kuhugi midagi jõuaks, samuti ei anta kasutajale mingit tagasisidet. | |||
"People" nupule vajutades avatakse aken, kus on kaks välja "Eesnimi" ja "Perekonnanimi" ja nende all kastike, need täites ning | |||
"Lisa" nuppu vajutades tekib kastikesse "Eesnimi Perekonnanimi", see tundub et tuleb APIst ja samuti tundub, et see saadetakse sinna. | |||
See kehtib kõikidele akendele, mida rakendus tekitab: Kui avada aken ning see sulgeda, siis ei viida mitte vana akna juurde sind, | |||
vaid avatakse uus aken, see tekitab olukorra, kus vahepeal on 3-4 MealPlanner rakenduse akent lahti. | |||
Sellega kasutaja kogemus piirdub, võiks olla natukene seletust, et kui palju rakendus peaks töötama ja mida selle rakendusega teha saab. | |||
Koodi poole pealt: | |||
Nagu öeldud kasutab rakendus WPF tehnoloogiat ning kõik vaated on kirjutatud XAML ja code-behind abil. Kõik *.xaml failid on projekti root kaustas | |||
ning see teeb peale vaatamise "koledaks", kui muidu on kasutatud kausta erinevate asjade grupeerimiseks (Models, Services), siis vaate failidega nii ei ole. | |||
Rakenduses on 3 põhikomponenti: vaated, Models (mille hulka kuuluvad ka ViewModels) ning Services. | |||
Vaadete konstrueerimiseks on kasutatud XAML-i ja code behindi, ühtlane ei ole see kuidas vaated räägivad viewmodelitega. | |||
IngredientsWindowVM hoiab endas loogikat API-ga suhtlemiseks ning teeb seda, aga nii ei ole see näiteks Login aknas, seal on küll | |||
ViewModelis üritatud seda lahendada, aga kood on välja kommenteeritud ning API-ga suhtlemise loogika on tõstetud Login vaate code behind-i. | |||
Models kaustas on mudelid, mis on rakenduses kasutuses ja samuti vaatemudelid, nende kohta polegi rohkem midagi öelda. Mudelid on oma olemuselt | |||
väga lihtsad ning väljendavad endas API poolt pakutavat. Mudelite propertied võiks by convention olla suurte esitähtedega. | |||
Vaatemudelid suhtlevad API-ga läbi Servicite ja edastavad saadud info vaatele. | |||
Serviced on klassid, mis suhtlevad otse API-ga kasutades selleks System.Net.Http pakki. Need serviced extendivad kõik BaseService-t, mis loob | |||
alguses HttpClient instantsi ning sätib baas URLi millega tuleks suhelda. Antud URL ei peaks olema string vaid võiks hoopis tulla confi failist, | |||
sealt oleks seda lihtsam muuta vajadusel. BaseService hoiab endas kahte meetodit, mida saab kasutada APIga suhtlemiseks, kas siis üle GET või POST päringu. | |||
Ülejäänud Serviced on oma olemuselt väga lihtsad ning kasutavad BaseService meetodeid, et saada seda mida nad ütlevad, et nad saavad. | |||
LoginService-i meetod PostLogin võtab sisse küll postData, aga ei tee selle muutujaga midagi võib-olla sellepärast autoril logimine tööle ei hakanudki. | |||
Servicites võiks olla ka error handling, mis saab siis kui http päring ei lähe, hetkel pole sellega arvestatud. | |||
Koodi üles ehitus on lihtne ja loogiline, kui mitte välja arvata Login-iga seotud, siis ka teevad klassid seda, mida nad peaksid tegema. | |||
Kasutatud on ka pärilikkust (Service: BaseService) ning mudelid ja vaatemudelid on erinevat, et ei kasutata samu asju. | |||
Koodi on lisatud ka üsna suures koguses kommentaari, mis on väga positiivne, kuna on näha et autor on kajastanud, mis probleemid tal võisid olla, | |||
see tagab selle, et kui järgmine kord koodile peale vaadata, siis on väga lihtne aru saada, miks just mingi asi siin on. | |||
Arvestades, et MealPlanner on koostatud ühe inimese poolt, siis on küllaltki palju jõutud. UI oleks võinud olla ühekeelne. | |||
== Logiraamat == | == Logiraamat == |
Latest revision as of 18:14, 11 June 2018
Meeskond ja rollid
- Siim Kallari (Arendaja, Projektijuht)
- Jorma Rebane (Arendaja)
- Andres Kelper (loobus)
Idee
- Ilmajaama põhine rakendus kus kasutaja saab isikustada, mida ta näha soovib.
- “Nice to have”-ina on võimalus kasutajal endal lisada oma ilmajaamu.
- “Nice to have”-ina maanteameti poolt antud info
Projekti TFS: TFS
Analüüs Link analüüsile
Lõpptoode Lõpptoode
XML Ülesanne
Ilmaandmete XML
<?xml version="1.0"?>
<WeatherStations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Stations>
<WeatherStation>
<Name>Tallinn-Harku</Name>
<Location>
<Name>Tallinn-Harku</Name>
<Latitude>59.398122222355134</Latitude>
<Longitude>24.602891666624284</Longitude>
</Location>
<Service>
<Name>Ilmateenistus</Name>
</Service>
<Observations>
<WeatherData>
<Phenomenon>Clear</Phenomenon>
<Visibility>20</Visibility>
<Precipitations>0</Precipitations>
<AirPressure>1024.4</AirPressure>
<RelativeHumidity>77</RelativeHumidity>
<AirTemperature>11</AirTemperature>
<WindDirection>40</WindDirection>
<WindSpeed>1.1</WindSpeed>
<WindSpeedMax>1.6</WindSpeedMax>
<WaterLevel>0</WaterLevel>
<WaterTemperature>0</WaterTemperature>
<UVIndex>0</UVIndex>
<AddedAt>2018-05-31T20:48:57Z</AddedAt>
</WeatherData>
</Observations>
</WeatherStation>
<WeatherStation>
<Name>Narva</Name>
<Location>
<Name>Narva</Name>
<Latitude>59.382777777111109</Latitude>
<Longitude>28.206666666666667</Longitude>
</Location>
<Service>
<Name>Ilmateenistus</Name>
</Service>
<Observations>
<WeatherData>
<Phenomenon />
<Visibility>0</Visibility>
<Precipitations />
<AirPressure>0</AirPressure>
<RelativeHumidity>0</RelativeHumidity>
<AirTemperature>11.1</AirTemperature>
<WindDirection>0</WindDirection>
<WindSpeed>0</WindSpeed>
<WindSpeedMax>0</WindSpeedMax>
<WaterLevel>128</WaterLevel>
<WaterTemperature>17.8</WaterTemperature>
<UVIndex>0</UVIndex>
<AddedAt>2018-05-31T20:48:57Z</AddedAt>
</WeatherData>
</Observations>
</WeatherStation>
<WeatherStation>
<Name>Tartu-Tõravere</Name>
<Location>
<Name>Tartu-Tõravere</Name>
<Latitude>58.264072222179834</Latitude>
<Longitude>26.461305555767481</Longitude>
</Location>
<Service>
<Name>Ilmateenistus</Name>
</Service>
<Observations>
<WeatherData>
<Phenomenon>Clear</Phenomenon>
<Visibility>20</Visibility>
<Precipitations>0</Precipitations>
<AirPressure>1024.4</AirPressure>
<RelativeHumidity>53</RelativeHumidity>
<AirTemperature>9.4</AirTemperature>
<WindDirection>99</WindDirection>
<WindSpeed>0.7</WindSpeed>
<WindSpeedMax>1.1</WindSpeedMax>
<WaterLevel>0</WaterLevel>
<WaterTemperature>0</WaterTemperature>
<UVIndex>0</UVIndex>
<AddedAt>2018-05-31T20:48:57Z</AddedAt>
</WeatherData>
</Observations>
</WeatherStation>
<WeatherStation>
<Name>Võru</Name>
<Location>
<Name>Võru</Name>
<Latitude>57.846277777020589</Latitude>
<Longitude>27.019505554963061</Longitude>
</Location>
<Service>
<Name>Ilmateenistus</Name>
</Service>
<Observations>
<WeatherData>
<Phenomenon />
<Visibility>20</Visibility>
<Precipitations>0</Precipitations>
<AirPressure>1024.3</AirPressure>
<RelativeHumidity>62</RelativeHumidity>
<AirTemperature>10</AirTemperature>
<WindDirection>148</WindDirection>
<WindSpeed>0.4</WindSpeed>
<WindSpeedMax>0.6</WindSpeedMax>
<WaterLevel>0</WaterLevel>
<WaterTemperature>0</WaterTemperature>
<UVIndex>0</UVIndex>
<AddedAt>2018-05-31T20:48:57Z</AddedAt>
</WeatherData>
</Observations>
</WeatherStation>
</Stations>
</WeatherStations>
Ilmaandmete XSD
<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="WeatherStations" nillable="true" type="WeatherStations" />
<xs:complexType name="WeatherStations">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Stations" type="ArrayOfWeatherStation" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArrayOfWeatherStation">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="WeatherStation" nillable="true" type="WeatherStation" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="WeatherStation">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Location" type="Location" />
<xs:element minOccurs="0" maxOccurs="1" name="Service" type="WeatherService" />
<xs:element minOccurs="0" maxOccurs="1" name="Observations" type="ArrayOfWeatherData" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Location">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="Latitude" type="xs:double" />
<xs:element minOccurs="1" maxOccurs="1" name="Longitude" type="xs:double" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="WeatherService">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArrayOfWeatherData">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="WeatherData" nillable="true" type="WeatherData" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="WeatherData">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Phenomenon" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="Visibility" type="xs:float" />
<xs:element minOccurs="0" maxOccurs="1" name="Precipitations" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="AirPressure" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="RelativeHumidity" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="AirTemperature" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="WindDirection" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="WindSpeed" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="WindSpeedMax" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="WaterLevel" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="WaterTemperature" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="UVIndex" type="xs:float" />
<xs:element minOccurs="1" maxOccurs="1" name="AddedAt" type="xs:dateTime" />
</xs:sequence>
</xs:complexType>
</xs:schema>
XSLT: XML->HTML Hetke ilm, sorteeritud kohanime järgi, eemaldatud puudulikud andmed
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<!-- Get weather info as a HTML table -->
<xsl:template match="/WeatherStations/Stations">
<html>
<body>
<h2>Hetke ilm, sorteeritud kohanime järgi, eemaldatud puudulikud andmed</h2>
<table border="1">
<tr bgcolor="#86add7">
<th>Asukoht</th>
<th>Temperatuur</th>
<th>Õhuniiskus</th>
</tr>
<xsl:for-each select="WeatherStation">
<xsl:sort select="Name"/>
<xsl:variable name="AirTemperature" select="Observations[1]/WeatherData/AirTemperature"/>
<xsl:if test="$AirTemperature != 0">
<tr>
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="$AirTemperature"/>°C</td>
<xsl:variable name="RelativeHumidity" select="Observations[1]/WeatherData/RelativeHumidity"/>
<xsl:choose>
<xsl:when test="$RelativeHumidity != 0">
<td><xsl:value-of select="$RelativeHumidity"/>%</td>
</xsl:when>
<xsl:otherwise>
<td>-</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
XSLT: XML->HTML Parimad rannad, sorteeritud veetemperatuuri järgi, kirjeldatud ilmaoluga
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<!-- Get best beaches as a HTML table -->
<xsl:template match="/WeatherStations/Stations">
<html>
<body>
<h2>Parimad rannad, sorteeritud veetemperatuuri järgi, kirjeldatud ilmaoluga</h2>
<table border="1">
<tr bgcolor="#86add7">
<th>Asukoht</th>
<th>Õhk</th>
<th>Vesi</th>
<th>Briis</th>
</tr>
<xsl:for-each select="WeatherStation">
<xsl:sort select="Observations[1]/WeatherData/WaterTemperature" data-type="number" order="descending"/>
<xsl:variable name="AirTemperature" select="Observations[1]/WeatherData/AirTemperature"/>
<xsl:variable name="WaterTemperature" select="Observations[1]/WeatherData/WaterTemperature"/>
<xsl:if test="$AirTemperature != 0 and $WaterTemperature != 0">
<tr>
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="$AirTemperature"/>°C</td>
<td><xsl:value-of select="$WaterTemperature"/>°C</td>
<xsl:variable name="WindSpeedMax" select="Observations[1]/WeatherData/WindSpeedMax"/>
<xsl:choose>
<xsl:when test="$WindSpeedMax > 2.8">
<td>Ränk tuul</td>
</xsl:when>
<xsl:when test="$WindSpeedMax > 1.5">
<td>Mõnus briis</td>
</xsl:when>
<xsl:otherwise>
<td>Vaikne</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Lõpptoote kasutusjuhend
Vajalikud seaded
- Install node.js
- VS2017 with ASP.NET and .NET Core support
WebOphelia
- WebOphelia -> Properties -> Debug -> App URL: http://localhost:5000/
- Run WebOphelia
OpheliaKlientRakendus
- npm install
- npm run start
- Hetkel otsitakse serverit aadressilt localhost:5000
Seeded
- Kaks kasutajat luuakse alguses, nendega saab klientrakenduses sisselogida:
- user@example.com, parool: 123456
- admin@example.com, parool: 123456
Funktsionaalsus
Sisse logimata kasutajale laetakse kaardile markerid, mis annavad ilmainfot erinevatest eesti paikadest, kui neile peale klikkida. Tavakasutaja + admin:
- Saab endale salvestada lemmik kohtasid ja neid eemaldada
Admin
- Näeb kasutajaid ning saab anda või ära võtta neilt õigusi
Klientrakenduse retsensioon meeskonnale MealPlannerSolo
Retsensioon meeskonnale MealPlanner Solo
MealPlanner klient on kirjutatud kasutatud WPF tehnoloogiat. Kui MealPlanner tööle panna avatakse avaleht, kus on võimalik sisse logida või endale kasutaja registreerida, tõsi küll kumbki nuppudest ei tööta. Järgmine aken kuvatakse 3 valikut, "Recipes", "Ingredients", "People" - mille kõrval on kõigil üks nupp. People nupp sõnastatud "Vaata", samas kui teised nupud hoiavad endas tavalit "Button" sõnastust. Samuti on segamini eesti ja inglise keel. Iga nupu peale klikkides avatakse eraldi vaade.
"Recipes" nupule vajutades, avatakse uus aken, kus on tühi kast ja nuppu "Avalehele", kahjuks sinna kasti ei õnnestunud midagi tekitada, seega ei oska öelda, kuidas täpselt see kastike toimima peaks.
"Ingredients" nupule vajutades avatakse jälle uus aken, kus on tühi kast ja 3 input välja (Name, Unit, Allergen) ja "Lisa" nupp, jällegi eesti ja inglise keel on segamine. Kui täita väljad ja vajutada nuppu "Lisa", siis natukese ajapärast aken sulgub ja kuigi API töötab tagataustal, ei ole näha et kuhugi midagi jõuaks, samuti ei anta kasutajale mingit tagasisidet.
"People" nupule vajutades avatakse aken, kus on kaks välja "Eesnimi" ja "Perekonnanimi" ja nende all kastike, need täites ning "Lisa" nuppu vajutades tekib kastikesse "Eesnimi Perekonnanimi", see tundub et tuleb APIst ja samuti tundub, et see saadetakse sinna.
See kehtib kõikidele akendele, mida rakendus tekitab: Kui avada aken ning see sulgeda, siis ei viida mitte vana akna juurde sind, vaid avatakse uus aken, see tekitab olukorra, kus vahepeal on 3-4 MealPlanner rakenduse akent lahti.
Sellega kasutaja kogemus piirdub, võiks olla natukene seletust, et kui palju rakendus peaks töötama ja mida selle rakendusega teha saab.
Koodi poole pealt:
Nagu öeldud kasutab rakendus WPF tehnoloogiat ning kõik vaated on kirjutatud XAML ja code-behind abil. Kõik *.xaml failid on projekti root kaustas ning see teeb peale vaatamise "koledaks", kui muidu on kasutatud kausta erinevate asjade grupeerimiseks (Models, Services), siis vaate failidega nii ei ole.
Rakenduses on 3 põhikomponenti: vaated, Models (mille hulka kuuluvad ka ViewModels) ning Services.
Vaadete konstrueerimiseks on kasutatud XAML-i ja code behindi, ühtlane ei ole see kuidas vaated räägivad viewmodelitega. IngredientsWindowVM hoiab endas loogikat API-ga suhtlemiseks ning teeb seda, aga nii ei ole see näiteks Login aknas, seal on küll ViewModelis üritatud seda lahendada, aga kood on välja kommenteeritud ning API-ga suhtlemise loogika on tõstetud Login vaate code behind-i.
Models kaustas on mudelid, mis on rakenduses kasutuses ja samuti vaatemudelid, nende kohta polegi rohkem midagi öelda. Mudelid on oma olemuselt väga lihtsad ning väljendavad endas API poolt pakutavat. Mudelite propertied võiks by convention olla suurte esitähtedega. Vaatemudelid suhtlevad API-ga läbi Servicite ja edastavad saadud info vaatele.
Serviced on klassid, mis suhtlevad otse API-ga kasutades selleks System.Net.Http pakki. Need serviced extendivad kõik BaseService-t, mis loob alguses HttpClient instantsi ning sätib baas URLi millega tuleks suhelda. Antud URL ei peaks olema string vaid võiks hoopis tulla confi failist, sealt oleks seda lihtsam muuta vajadusel. BaseService hoiab endas kahte meetodit, mida saab kasutada APIga suhtlemiseks, kas siis üle GET või POST päringu. Ülejäänud Serviced on oma olemuselt väga lihtsad ning kasutavad BaseService meetodeid, et saada seda mida nad ütlevad, et nad saavad.
LoginService-i meetod PostLogin võtab sisse küll postData, aga ei tee selle muutujaga midagi võib-olla sellepärast autoril logimine tööle ei hakanudki.
Servicites võiks olla ka error handling, mis saab siis kui http päring ei lähe, hetkel pole sellega arvestatud.
Koodi üles ehitus on lihtne ja loogiline, kui mitte välja arvata Login-iga seotud, siis ka teevad klassid seda, mida nad peaksid tegema. Kasutatud on ka pärilikkust (Service: BaseService) ning mudelid ja vaatemudelid on erinevat, et ei kasutata samu asju.
Koodi on lisatud ka üsna suures koguses kommentaari, mis on väga positiivne, kuna on näha et autor on kajastanud, mis probleemid tal võisid olla, see tagab selle, et kui järgmine kord koodile peale vaadata, siis on väga lihtne aru saada, miks just mingi asi siin on.
Arvestades, et MealPlanner on koostatud ühe inimese poolt, siis on küllaltki palju jõutud. UI oleks võinud olla ühekeelne.
Logiraamat
01.06.2018
Viimase hetke paanika. XML+XSD lisatud.
27.02.2018
Projekti analüüs Siim: Analüüsi viimistlemine Jorma/Andres: Project setup Järgmine koosolek: 06.03.2018 19:00
20.02.2018
Projekti esimene läbirääkimine, timeline paika. Skoop paika, üldine arusaam missugune projekt tuleb. Siim: Itcollege wiki Andres: TFS setup Jorma: Räägib hr. Poskaga, et teha varem valmis projekt, et lõpetada saaks. Lisaks järgmiseks koosolekuks mõelda projekti arhitektuurilise poole peale
11.02.2018
Meeskonna loomine, arutelu mida teha