Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DNCR-27] Course creation #25

Merged
merged 17 commits into from
Oct 11, 2016
Merged

[DNCR-27] Course creation #25

merged 17 commits into from
Oct 11, 2016

Conversation

tlegutko
Copy link
Owner

@tlegutko tlegutko commented Oct 2, 2016

Things not yet working / known issues:

  1. css detail - I couldn't make 'save' button in disabled state, not that important.
  2. css detail - when there are multiple errors in backend form validation, it'll look sloppy, but it's unlikely, so not that important
  3. css detail - I couldn't make a placeholder for instructor and location options, I tried hidden and disabled and selected, but couldn't figure it out in reasonable time
  4. I didn't add proper datepicker, but created task for it: https://trello.com/c/hdWq1x8D
  5. I added task for course-edition https://trello.com/c/iyCC4DRn/104-mened-er-edycja-kursu
  6. Instructors should have many-to-many relation with courses, but @szimin18 did a lot of changes in instructors area in his task, so I created separate task for it: https://trello.com/c/Lov1fALb
  7. Mobile view looks bad. I can create separate task for fixing it - I didn't want to spend even more time on tihs task (and block several other tasks) and I think it's not our top top priority now. But maybe some different columns decisions could make a quick fix?

// TODO remove mock company_id above and inject real one once DNCR-92 is merged
$course = Course::create($courseData);

$start_date_from_request = ['start_date' => (new DateTime($request->start_time))->format('Y-m-d')];
Copy link
Collaborator

@megawebmaster megawebmaster Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either camelCase or snake_case and I think we stick to the camelCase.

return response()->json(Course::create($request->all()));
$courseData = $request->only('name', 'price', 'classes_count', 'description', 'seats_count') + ['company_id' => 1];
// TODO remove mock company_id above and inject real one once DNCR-92 is merged
$course = Course::create($courseData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -100,7 +100,7 @@
'provider' => 'users',
'email' => 'auth.emails.password',
'table' => 'password_resets',
'expire' => 60,
'expire' => 120,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you prolong token validation? We need to add token refreshing in our frontend app, that's all ;)

@@ -7,7 +7,7 @@ import { Component, Input } from '@angular/core';
<div class="form-group" [class.has-danger]="errors">
<ng-content></ng-content>
<div class="form-control-feedback" *ngIf="errors">
<span *ngFor="let error of errors">{{ error }}</span>
<span *ngFor="let error of errors">{{ error }} </span>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to be converted into a list, but as agreed - we don't have to cover this right now.

@@ -21,3 +24,62 @@ export class CourseTime {
export class CourseEvent {
date: string;
}

export class CreateCourseRequest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but I think this is equivalent to Course model, isn't it? The Course should have required fields for submodels (like CourseTime). Why did you add this class?

Copy link
Owner Author

@tlegutko tlegutko Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment of creating this there was no Course model and I thought it'd be easier to create flat request structure and make it nested later when adding multiple courseTimes (https://trello.com/c/JtGMgJvu). It's up to you - should I change it now or leave it for mentioned task?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, leave it for now, let's just proceed with enhancements.

return events;
public ngOnInit(): void {
this.service.calendarEvents().subscribe((events) => this.events = events);
this.service.courseUpdates.subscribe(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this should be called courseCreated instead of courseUpdates - because in case of update you need to get whole list and re-render everything. In case of creation - just add the events as you do here.

@@ -43,6 +44,7 @@ export class ManagerCalendarComponent {

onDayClick(e: CalendarDayClick) {
console.log('clicked on day in manager with date: ' + e.date.format());
this.router.navigate(['/manager/courses']);
this.service.broadcastCalendarDateClick(e.date);
Copy link
Collaborator

@megawebmaster megawebmaster Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said earlier - this should be replaced with calendar event (I mean @Output() here) so the component above properly handles the click. This will allow us to easier control what is done where and how it works in general (see how React is built for example - with explicit control and data flows).

Copy link
Owner Author

@tlegutko tlegutko Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, the thing is now it's only a click that redirects to page / updates the date input. Do you mean to create and insert event on calendar on click (as google calendar does on click) and broadcast it instead of date only? I think it'd look good, but maybe let's do a separate task for it? There are some quirks as to how to handle title, connect drag & drop with create-course form etc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said on Slack - I wanted it to be more tree-like passing data, but it seems impossible to do. Leave it as it is now.

@@ -43,6 +44,7 @@ export class ManagerCalendarComponent {

onDayClick(e: CalendarDayClick) {
console.log('clicked on day in manager with date: ' + e.date.format());
this.router.navigate(['/manager/courses']);
this.service.broadcastCalendarDateClick(e.date);
this.router.navigate(['/manager/courses/create-course']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This navigation should also be moved to the upper component as the calendar should display data only, not redirect.

Copy link
Owner Author

@tlegutko tlegutko Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't yet feel that it's wrong, but I'll trust you with handling it outside of manager-calendar.

On the other hand - how about redirection in CoursesService? I'm having this problem in DNCR-103, where there is course details (& edit) page and Save button is separated from CourseEditComponent (CreateCourseComponent in this task) with <router-outlet> and Service seems like the best place to handle both request and redirection, so the same action (creation and update) is handled in one place.

I may have put it in a convoluted way, bottom line is - do you think it's a good idea to handle route changes in service?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't. I think it should be like this: this.service.update(course).subscribe((course) => this.router.navigate(...));. Why? Because the service is responsible for updating the entity only (SRP) and the component is responsible for proper redirection in case of success.

Copy link
Owner Author

@tlegutko tlegutko Oct 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I started migrating code that navigates router from manager-calendar to ManagerCoursesComponent (it's parent) and I stopped, because it seems pointless. Think about it. We have CalendarComponent which wraps calendar from ngPrime. So it only displays data and outputs changes with our interfaces, okay. Then we have ManagerCalendarComponent and ReceptionCalendarComponent which add little customization (default view, ability to edit) and are components surrounding calendar, they subscribe to course service to fetch & update data.
And I see three approaches:

  1. they handle actions on clicks, whether it's notification to courses.service or route change, as it is now.
  2. they broadcast all changes and pass event handling to parent component, but then ManagerCoursesComponent and Reception have to duplicate all event handling interfaces and ManagerCalendar and ReceptionCalendar are additional layer of wrappers that doesn't do much.
  3. handle some of the events and leave some handling to upper component (so in this commented line - should ManagerCalendar tell service to broadcast click and then emit change so it's parent changes route? where's the line of responsibility? now that sounds like breaking SRP to me.

I think you're expecting something between 2 and 3, I don't see it clearly. I think leaving 1 is the simplest approach.

EDIT: And think about encapsulating calendar interface - I think it'd be good for ManagerCalendar and ReceptionCalendar to be the last ones to have to know about calendar details. And then all the calendar-handling-logic is in one place and we don't have additional layer of abstraction that does almost nothing. The 'dumb displaying of data and propagating events' is already done in CalendarComponent..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't agree with you on that as you seem to miss the point. How can calendar know what to do with selected course? It's up to the container to decide and this is why ManagerCalendarComponent became responsible for multiple purposes: showing data (with fetching them!) and navigating to details page (what if we wanted it to behave differently when used in different place?). The reason to abstract it is to make sure that we can reason about the program easily and this means we can open ManagerCoursesComponent to know what we want to know.

This is course clicked event rather than date clicked event so we are abstracting things and pushing "hey, somebody clicked on course 1" to the parent.

BTW. I really would like to hear me as I worked on large project in AngularJS and I know this simple things will make it far easier to update and maintain in the future.

],
imports: [
BrowserModule, FormsModule, RouterModule.forChild(routes), CommonsModule, AttendeeModule, CourseModule
],
providers: [
CourseResolve, InstructorsService, LocationsService
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to add CourseResolve here?

@@ -0,0 +1,15 @@
app > span.loader {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was in .git-ignore and was removed on purpose. Please remove it again.

… frontend. Todo csses and removal of errors.
{
$course = Course::create($courseData);
$courseTime = $course->times()->create($courseTimeData);
$courseTime->course()->associate($course);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line needed? I thought when you do $course->times()->create() it properly associates it back to itself.

{
$date = clone($start_date);
$event = new CourseEvent(['date' => $date->add(new DateInterval("P{$week}W"))]);
$event->courseTime()->associate($courseTime);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of these 2 lines you could do $courseTimes->events()->create(...); - we have transaction so it does not hurt us anymore.

return $course;
});

$course->load('times.events');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? We have added everything as we should so it has all the data it needs, right?

Copy link
Owner Author

@tlegutko tlegutko Oct 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need it because this single returned course is added to calendar in front-end, so we need it's times and events so CalendarItems can be parsed from it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But as you see the $course already contains all events and times you need, don't you?

@@ -21,3 +24,62 @@ export class CourseTime {
export class CourseEvent {
date: string;
}

export class CreateCourseRequest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, leave it for now, let's just proceed with enhancements.

constructor(private http: AuthHttp) {
this.recentlyClickedTimeSource.next(CreateCourseTime.defaultTime()); // default for page refresh
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, as this is a prototype simply remove this line. We won't ever need default time and when we click to create term - we just get the time we want, that's all ;)


<p class="alert alert-danger" role="alert" *ngIf="error">{{ error }}</p>
<form-field [errors]="errors.startTime" class="col-xs-12 row moved-errors">
<label for="startTime" class="col-xs-3 col-xl-2 col-form-label test">Rozpoczęcie</label>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explained on Slack - if you need help here please ask ;)


public ngOnInit() {
this.model.repeatWeeksCount = 1;
this.coursesService.recentlyClickedTime.subscribe(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have strong feeling it does not belong here, but this should be updated when multiple terms are being worked on.

@@ -43,6 +44,7 @@ export class ManagerCalendarComponent {

onDayClick(e: CalendarDayClick) {
console.log('clicked on day in manager with date: ' + e.date.format());
this.router.navigate(['/manager/courses']);
this.service.broadcastCalendarDateClick(e.date);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said on Slack - I wanted it to be more tree-like passing data, but it seems impossible to do. Leave it as it is now.

@@ -43,6 +44,7 @@ export class ManagerCalendarComponent {

onDayClick(e: CalendarDayClick) {
console.log('clicked on day in manager with date: ' + e.date.format());
this.router.navigate(['/manager/courses']);
this.service.broadcastCalendarDateClick(e.date);
this.router.navigate(['/manager/courses/create-course']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't. I think it should be like this: this.service.update(course).subscribe((course) => this.router.navigate(...));. Why? Because the service is responsible for updating the entity only (SRP) and the component is responsible for proper redirection in case of success.

@@ -68,14 +77,22 @@ export const routes = [
ManagerCoursesSingleComponent,
ManagerCoursesDetailsComponent,
ManagerCoursesAttendeesComponent,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line here? :)

…Improved observables code style in CoursesService. Removed toast error handling and added date check.
<option *ngFor="let i of instructors" [value]="i.id">{{i.name + ' ' + i.surname}}</option>
</select>
</labelled-form-field>
<labelled-form-field [errors]="errors.locationId" [labelFor]="model.locationId" [label]="'Sala'">
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before you comment that it could be just [label]="Sala" -> yes it could, but in most of other places we have either space or polish character with which this syntax doesn't work and single quotes are needed, so I think it's better to just use the same syntax in whole file.

…our. Course title responsivness left for some other time.
return $course;
});

$course->load('times.events');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But as you see the $course already contains all events and times you need, don't you?

selector: 'labelled-form-field',
template: `
<form-field [errors]="errors" class="row m-r-0">
<label for="labelFor" class="col-xs-12 col-sm-3 col-xl-2 col-form-label">{{ label }}</label>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for="{{ labelFor }}"

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
a tutaj znalazłem wyjaśnienie, czemu angular/angular#3803
ale na szczęście [attr.for]="labelFor" już śmiga. przy okazji znalazłem buga, że podawałem tam pole modelu, a nie id obiektu, więc podwójne dzięks :).

)
export class LabelledFormField {
@Input() errors: string[] = [];
@Input() labelFor: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simple for?

@@ -0,0 +1,33 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { InstructorsService } from '../../../instructors/instructors.service';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks ;)

@@ -43,6 +44,7 @@ export class ManagerCalendarComponent {

onDayClick(e: CalendarDayClick) {
console.log('clicked on day in manager with date: ' + e.date.format());
this.router.navigate(['/manager/courses']);
this.service.broadcastCalendarDateClick(e.date);
this.router.navigate(['/manager/courses/create-course']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't agree with you on that as you seem to miss the point. How can calendar know what to do with selected course? It's up to the container to decide and this is why ManagerCalendarComponent became responsible for multiple purposes: showing data (with fetching them!) and navigating to details page (what if we wanted it to behave differently when used in different place?). The reason to abstract it is to make sure that we can reason about the program easily and this means we can open ManagerCoursesComponent to know what we want to know.

This is course clicked event rather than date clicked event so we are abstracting things and pushing "hey, somebody clicked on course 1" to the parent.

BTW. I really would like to hear me as I worked on large project in AngularJS and I know this simple things will make it far easier to update and maintain in the future.

courseCreated = this.courseCreatedSource.asObservable();
calendarItemsCreated = this.courseCreated.map(this.courseToCalendarItems);

recentlyClickedTimeSource = new ReplaySubject<CreateCourseTime>(1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be private as well.


createCourse() {
this.coursesService.create(this.model).subscribe(
(course) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know you don't need braces if you have single line command? :)

this.router.navigate(['/reception/course-details', course.id]);
}

onDayClick(moment: Moment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this :)

@megawebmaster megawebmaster merged commit ef2803d into develop Oct 11, 2016
@tlegutko tlegutko deleted the DNCR-27 branch October 11, 2016 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants