Advertisement
  1. Code
  2. Yii

Xây Dựng Trang Khởi Nghiệp: Những Lệnh Lên Lịch Hẹn Nâng Cao

Scroll to top
Read Time: 19 min
This post is part of a series called Building Your Startup With PHP.
Building Your Startup: Automatic Time-Zone Detection
Building Your Startup: Requesting Scheduling Changes

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Bài hướng dẫn này là một phần của loạt bài Building Your Startup with PHP trên Envato Tuts+. Trong loạt bài này, tôi sẽ hướng dẫn bạn xuất bản một trang khởi nghiệp từ ý tưởng đến hiện thực sử dụng ứng dụng Meeting Planner của tôi làm một ví dụ thực tế. Mỗi bước trong quá trình, tôi sẽ phát hành code của Meeting Planner làm ví dụ về mã nguồn mở mà bạn có thể học từ đó. Tôi cũng sẽ giải quyết các vấn đề kinh doanh liên quan đến khởi nghiệp khi phát sinh.

Mở rộng chọn lựa lên lịch

Khi Meeting Planner alpha bắt đầu giai đoạn kiểm tra, tính năng thiếu sót rõ nhất là không có khả năng thay đổi cuộc hẹn sau khi nó đã được lên lịch. Nhưng cũng có những tính năng còn thiếu khác như gửi lại những lời mời bị mất trong email, tái lập lịch một cuộc hẹn một cách toàn diện, hoặc khả năng xem và chỉnh sửa những thiết lập điều khiển đã chọn bởi người tổ chức.

Thật thú vị, tôi cũng đã bắt đầu nhận ra rằng khả năng điều chỉnh các cuộc họp một cách dễ dàng sau khi chúng đã được lên kế hoạch có thể làm nên hoặc phá vỡ thương hiệu Meeting Planner. Ví dụ, có rất nhiều kỹ thuật xã hội trong điều chỉnh cuộc họp sau khi lập kế hoạch. Thông thường, bạn cần yêu cầu người tham gia liệu có ổn không khi điều chỉnh thời gian hoặc địa điểm. Có lẽ bạn chỉ muốn gặp sớm hơn 15 phút hoặc vào ngày hôm với cùng một thời gian và địa điểm. Nhưng bạn không thể luôn thực hiện những thay đổi này nếu không có sự đồng ý.

Giữ trang web dễ hiểu và đơn giản để sử dụng với tất cả các khả năng này là định hướng chính của tôi. Làm thế nào tôi có thể thêm một số tính năng ngày càng tăng mà không cluttering giao diện người dùng hoặc quá che giấu chúng? Làm thế nào nó sẽ làm việc trên cả hai giao diện di động và máy tính để bàn?

Trong bài hướng dẫn hôm nay, tôi sẽ mở rộng thanh điều hướng bằng cách sử dụng Bootstrap và các khái niệm cơ bản về xây dựng một số tính năng lên lịch nâng cao trong Meeting Planner. Tuần tới tôi sẽ xem xét xây dựng một tính năng phức tạp hơn cho người tham gia để yêu cầu thay đổi và để người khác chấp nhận hoặc từ chối những thay đổi đó.

Tôi hy vọng bạn sẽ thử tất cả tính năng lên lịch mới trên trang web trực tuyến và chia sẻ suy nghĩ và phản hồi của bạn trong phần nhận xét bên dưới. Tôi tham gia vào các cuộc thảo luận, nhưng bạn cũng có thể tìm tôi trên Twitter qua @reifman . Tôi luôn đón nhận các ý tưởng về tính năng mới cho Meeting Planner, cũng như các đề xuất cho các loạt bài trong tương lai.

Như một lời nhắc nhở, tất cả các code cho Meeting Planner được cung cấp là mã nguồn mở và được viết bằng framework Yii2 của PHP. Nếu bạn muốn tìm hiểu thêm về Yii2, xem qua loạt bài song song của tôi Lập trình Với Yii2.

Mở rộng thanh điều hướng

Đầu tiên, hãy xem làm sao để mở rộng thanh điều hướng hiện tại. Đây là danh sách của các tính năng tôi sẽ lên kế hoạch để bổ sung:

  • Cho phép thay đổi địa điểm, ngày và giờ của một cuộc họp sau khi được hoàn tất.
  • Việc yêu cầu những điều chỉnh nhỏ trong một cuộc họp, chẳng hạn có thể gặp sớm hơn một giờ? Hoặc đề xuất địa điểm khác?
  • Việc tái sắp xếp toàn diện một cuộc họp với cùng người tham dự và địa điểm.
  • Lập lại một cuộc họp bằng việc dùng địa điểm, những ngày trong tuần và thời gian trong ngày cùng những người tham dự của cuộc họp trước để thực hiện việc lên lịch một cuộc họp mới dễ dàng hơn.
  • Cho người tham dự thấy một lịch sử theo thứ tự của tất cả những hoạt động lên kế hoạch cho một cuộc họp.
  • Xem và cập nhật các thiết lập cho một cuộc họp

Như bạn có thể thấy, không chỉ có nhiều chức năng để xây dựng, mà tôi còn không biết đặt nó ở đâu trong giao diện mà không gây ra sự lộn xộn.

Command bar cũng cần thay đổi phụ thuộc vào trạng thái của cuộc họp. Những cuộc họp trong giai đoạn lên kế hoạch, có những chọn lựa khác biệt ngoài pending, confirmed hoặc những cuộc họp đã hoàn tất.

Những ý tưởng UX ban đầu đưa trở về Bootstrap

Ý tưởng ban đầu của tôi để cung cấp một liên kết Advanced Settings (những thiết lập nâng cao) sẽ hiển thị một thanh lệnh ẩn. Tôi đã trải nghiệm điều này trước tiên, nhưng nó không thoã đáng về mặt thẩm mỹ.

Sau đó, tôi xét lại tài liệu của Bootstrap và tìm thấy dropdown combo box:

Build Your Startup Advanced Scheduling - Bootstrap Documentation of DropdownBuild Your Startup Advanced Scheduling - Bootstrap Documentation of DropdownBuild Your Startup Advanced Scheduling - Bootstrap Documentation of Dropdown

Tôi thích cách nó hoạt động. Vì thế tôi quyết định đặt phần lần những lệnh nâng cao trong một nút dropdown hướng bên trái.

Đây là một ví dụ về diện mạo của nó trong giai đoạn lên kế hoạch của một cuộc họp:

Build Your Startup Advanced Scheduling - Meeting Planner Button Dropdown Build Your Startup Advanced Scheduling - Meeting Planner Button Dropdown Build Your Startup Advanced Scheduling - Meeting Planner Button Dropdown

Chú ý rằng bootstrap đã đề xuất một class cho những menu dạng dropdown. Một command bar đặt ở cuối trang sử dụng dropup như bên dưới:

1
/* the partial position knows to set the dropclass variable up or down */
2
echo $this->render('_command_bar_past', [
3
    'model'=>$model,
4
    'isPast'=>true,
5
    'dropclass'=>'dropup',
6
    'isOwner' => $isOwner,
7
]);
8
9
/* the resulting view applies the dropclass */
10
<div class="command-bar">
11
  <div class="row">
12
    <div class="col-xs-4">
13
      <div class="<?= $dropclass ?>" >
14
      <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
15
      <?= Yii::t('frontend','Options');?>
16
      <span class="caret"></span>
17
      </button>
18
      <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
19
        <?php
20
          if (!$isPast && ($model->viewer == Meeting::VIEWER_ORGANIZER || $meetingSettings->participant_reopen)) {
21
            ?>
22
            <li><?= Html::a(Yii::t('frontend', 'Make changes'), ['reopen','id'=>$model->id],
23
             ['title'=>Yii::t('frontend','tbd')]); ?></li>

Tôi cũng đã tạo các file view thành phần được hiển thị tùy thuộc vào trạng thái của một cuộc họp. Ví dụ, trong /frontend/views/meeting/view_confirmed.php, bạn có thể thấy _command_bar_past.php hoặc _command_bar_confirmed.php được đính kèm:

1
<?php
2
    if ( $model->status >= $model::STATUS_COMPLETED) {
3
      ...
4
      echo $this->render('_command_bar_past', [
5
          'model'=>$model,
6
          'isPast'=>true,
7
          'dropclass'=>'dropdown',
8
          'isOwner' => $isOwner,
9
      ]);
10
    } else {
11
      echo $this->render('_command_bar_confirmed', [
12
          'model'=>$model,
13
          'meetingSettings' => $meetingSettings,
14
          'showRunningLate'=>$showRunningLate,
15
          'isPast'=>$isPast,
16
          'dropclass'=>'dropdown',
17
          'isOwner' => $isOwner,
18
      ]);
19
    }
20
  ?>

Xác định quyền truy cập vào các lệnh cho những người xem khác nhau

Tôi không muốn cho phép mọi người nhìn thấy tất cả các lệnh. Những người tổ chức sẽ thấy nhiều lệnh hơn so với người tham gia, nhưng các thiết lập cuộc họp cũng thường cho phép họ truy xuất.

Dưới đây là ví dụ để xác định xem có nên hiển thị tùy chọn để mở lại cuộc họp và thay đổi nó như thể nó vẫn đang trong giai đoạn lập kế hoạch:

1
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
2
  <?php
3
    if (!$isPast && ($model->viewer == Meeting::VIEWER_ORGANIZER
4
       || $meetingSettings->participant_reopen)) {
5
   ?>
6
      <li><?= Html::a(Yii::t('frontend', 'Make changes'), ['reopen','id'=>$model->id],
7
          ['title'=>Yii::t('frontend','tbd')]); ?></li>
8
        

Chọn lựa này bao gồm trong menu thả xuống của Bootstrap nếu cuộc họp chưa bắt đầu và người xem là người tổ chức hoặc người tổ chức đã cho phép người tham gia thay đổi.

Bây giờ, chúng ta hãy dấn thân vào việc xây dựng một số chức năng cho các lệnh khác nhau này.

Xây dựng những câu lệnh sắp xếp nâng cao

Không khả thi nếu đề cập tất cả công việc mới đi vào chi tiết của những tính năng sắp xếp lịch này. Ngắn gọn, tôi sẽ chỉ bao gồm những điều cơ bản và bất kỳ khía cạnh độc đáo của các lệnh khác nhau.

Mỗi tính năng cần phải lâu hơn bây giờ

Sau khi triển khai một số tính năng bảo mật cao hơn trong Meeting Planner, tôi cố gắng bám vào những tiêu chuẩn code nghiêm ngặt hơn khi tôi bổ sung thêm tính năng. Tôi dành nhiều thời gian vào việc truy xuất controller, kiểm tra truy xuất model, và giới hạn đánh giá.

Bản phát hành beta cũng sẽ hỗ trợ nhiều người tham dự trong cuộc họp, và trong đầu tôi phải kiến trúc code khi tiến hành.

Tổng quan, với mỗi thứ tôi mất nhiều thời gian thực hiện so với chế độ tạo hình mẫu nhanh.

Khi tôi xây dựng những tính năng đa dạng cho cuộc họp, tôi thường xuyên cảm thấy lượng công việc tăng lên khi tôi bổ sung vào phần code nền tảng. Bạn sẽ chú ý những phần kiểm tra này trong code bên dưới.

Cũng có sự chú ý đến nhật ký lịch sử Meeting, chúng ta sẽ làm cho nó hiển thị. Vì thế mỗi hành động thường yêu cầu một kỷ lục ghi chép hữu ích

Thực hiện thay đổi cho một cuộc họp

Một cách dễ dàng cho phép thay đổi một cuộc họp đã được chốt là chỉ cho phép người tổ chức mở lại nó, đưa nó về lại bước planning. Sau đó, họ có thể bổ sung, ngày giờ và địa điểm, chọn những cái mới, và quyết định cuộc họp lại.

Tôi cũng muốn người tổ chức cấp quyền cho người tham dự làm điều này. Việc này đỏi hỏi phải xây dựng khả năng xem và cập nhật thiết lập cho mỗi cuộc họp.

Tôi đã bổ sung cho phép người tham dự để yêu cầu thay đổi của hoặc thực hiện trực tiếp.

Khi một cuộc họp được tạo ra, thiết lập của nó được khởi tạo với thiết lập mặc định của người dùng:

1
public function initializeMeetingSetting($meeting_id,$owner_id) {
2
  $checkMtgStg = MeetingSetting::find()->where(['meeting_id' => $meeting_id])->one();
3
  if (is_null($checkMtgStg)) {
4
    // load meeting creator (owner) user settings to initialize meeting_settings

5
    UserSetting::initialize($owner_id); // if not initialized

6
    $user_setting = UserSetting::find()->where(['user_id' => $owner_id])->one();
7
    $meeting_setting = new MeetingSetting();
8
    $meeting_setting->meeting_id = $meeting_id;
9
    $meeting_setting->participant_add_place=$user_setting->participant_add_place;
10
    $meeting_setting->participant_add_date_time=$user_setting->participant_add_date_time;
11
    $meeting_setting->participant_choose_place=$user_setting->participant_choose_place;
12
    $meeting_setting->participant_choose_date_time=$user_setting->participant_choose_date_time;
13
    $meeting_setting->participant_finalize=$user_setting->participant_finalize;
14
    $meeting_setting->participant_reopen=$user_setting->participant_reopen;
15
    $meeting_setting->participant_request_change=$user_setting->participant_request_change;
16
    $meeting_setting->save();
17
  }
18
}

Bạn có thể xem kết quả của trình điều khiển mới và cập nhật chế độ xem cho các thiết lập Gặp gỡ dưới đây:

Build Your Startup Advanced Scheduling - Meeting SettingsBuild Your Startup Advanced Scheduling - Meeting SettingsBuild Your Startup Advanced Scheduling - Meeting Settings

Đây là hàm actionReopen() trong /frontend/controllers/MeetingController.php:

1
public function actionReopen($id) {
2
  $m = $this->findModel($id);
3
  $m->setViewer();
4
  // also check reopen()

5
  if ($m->viewer == Meeting::VIEWER_ORGANIZER || $m->meetingSettings->participant_reopen) {
6
    if ($m->reopen()) {
7
        Yii::$app->getSession()->setFlash('success', Yii::t('frontend','The meeting has now been reopened so you can make changes.'));
8
    } else {
9
        Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you are not allowed to reopen a meeting this many times. Try creating a new meeting.'));
10
    }
11
  } else {
12
    Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you are not allowed to do this.'));
13
  }
14
  return $this->redirect(['view', 'id' => $id]);
15
}

Và đây là code cho model Meeting.php để dời cuộc họp lại sang chế độ planning (lên kế hoạch):

1
public function reopen() {
2
  // when organizer or participant with permission asks to make changes

3
  if (MeetingLog::withinActionLimit($this->id,MeetingLog::ACTION_REOPEN,Yii::$app->user->getId(),7)) {
4
    $this->status = Meeting::STATUS_SENT;
5
    $this->update();
6
    $this->increaseSequence();
7
    MeetingLog::add($this->id,MeetingLog::ACTION_REOPEN,Yii::$app->user->getId());
8
    return true;
9
  } else {
10
    // over limit per meeting

11
    return false;
12
  }
13
}

withinActionLimit đang kiểm tra số lần ai đó cố thử mở lại một cuộc họp. IncreaseSequence để dành cho file .ics - khi ngày, giờ và địa điểm thay đổi, file ics cần được biết.

Hình ảnh bên dưới hiển thị một cuộc họp đã được xác nhận với nhiều chọn lựa cao cấp cấp có sẵn:

Build Your Startup Advanced Scheduling - Command bar with fully loaded Dropdown menuBuild Your Startup Advanced Scheduling - Command bar with fully loaded Dropdown menuBuild Your Startup Advanced Scheduling - Command bar with fully loaded Dropdown menu

Khi người dùng click vào Make Changes trong menu phía trên, trạng thái cuộc họp trợ về đang lên kế hoạch và họ có thể trở lại để cập nhật ngày, giờ và nơi chốn:

Build Your Startup Advanced Scheduling - Reopened Meeting PlanningBuild Your Startup Advanced Scheduling - Reopened Meeting PlanningBuild Your Startup Advanced Scheduling - Reopened Meeting Planning

Sắp xếp lại cho một cuộc họp

Nếu các sự kiện đã dẫn dắt các người tham dự nhận ra rằng họ chỉ cần bắt đầu lại, chọn lựa Reschedule huỷ cuộc họp hiện tại và tạo một lời mời lập kế hoạch mới.

Hiện thời, tôi giới hạn tình năng này chỉ dành cho người tổ chức (không cho người tham dự). Phương thức Meeting.php::Reschedule() hỗ trợ mỗi người thực hiện hành động này:

1
    public function reschedule() {
2
      $newOwner = $user_id = Yii::$app->user->getId();
3
      // user can only cancel their own Meeting

4
      if ($this->owner_id == $user_id) {
5
        $addParticipant = false;
6
        $this->cancel($user_id);
7
        MeetingLog::add($this->id,MeetingLog::ACTION_RESCHEDULE,$user_id);
8
      } else {
9
          // if user is participant - needs to reverse

10
          if (!isAttendee($this->id,$user_id)) {
11
            // user isn't owner or participant - error

12
            return false;
13
          } else {
14
            // reverse the owner and participant

15
            $addParticipant = $this->owner_id;
16
          }
17
      }
18
      // create new meeting - as copy of old meeting

19
      $m = new Meeting();
20
      $m->attributes = $this->attributes;
21
      $m->owner_id = $newOwner;
22
      $m->status = Meeting::STATUS_PLANNING;
23
      $m->created_at = time();
24
      $m->updated_at = time();
25
      $m->logged_at = 0;
26
      $m->cleared_at = 0;
27
      $m->sequence_id = 0;
28
      $m->save();
29
      // clone the selected place (not all of them)

30
      $chosenPlace = $this->getChosenPlace($this->id);
31
      if ($chosenPlace!==false) {
32
        $mp = new MeetingPlace;
33
        $mp->suggested_by = $newOwner;
34
        $mp->attributes = $chosenPlace->attributes;
35
        $mp->meeting_id = $m->id;
36
        $mp->created_at = time();
37
        $mp->updated_at = time();
38
        $mp->save();
39
      }
40
      // clone the participants

41
      foreach ($this->participants as $p) {
42
        // skip if reschedule new owner was a participant

43
        if ($p->participant_id==$user_id) {
44
          continue;
45
        }
46
        // note Participant afterSave will create choices for place

47
        $clone_p = new Participant();
48
        $clone_p->attributes = $p->attributes;
49
        $clone_p->email = User::findOne($p->participant_id)->email;
50
        $clone_p->meeting_id = $m->id;
51
        $clone_p->invited_by = $newOwner;
52
        $clone_p->status = Participant::STATUS_DEFAULT;
53
        $clone_p->created_at = time();
54
        $clone_p->updated_at = time();
55
        $clone_p->save();
56
      }
57
      // if participant asked to reschedule - not yet allowed

58
      if ($addParticipant!==false) {
59
        $newP = new Participant();
60
        $newP->meeting_id = $m->id;
61
        $newP->participant_id = $addParticipant;
62
        $newP->invited_by = $user_id;
63
        $newP->status = Participant::STATUS_DEFAULT;
64
        $newP->created_at = time();
65
        $newP->updated_at = time();
66
        $newP->save();
67
      }
68
      return $m->id;
69
    }

Những người tham dự và địa điểm đã chọn lựa được sao chép thành một cuộc họp mới.

Lập lại một cuộc họp

Một cách tiếp cận khác cho việc lên kế hoạch là cho phép những người tham dự có thể sao chép những cuộc họp trước đây. Trong tương lai, có lẽ tôi cho phép người xem quyết định những người dự, địa điểm và thời gian nào được sao chép, nhưng hiện giờ, Meeting Planner tạo một cuộc họp mới với cùng những người tham dự ở cùng một địa điểm - và một cùng một ngày trong tuần và thời gian của ngày thứ nhất và hai tuần trong tương lai.

1
public function repeat() {
2
      // to do - expand repeat meeting to have more options

3
      // e.g. pick same day and time in future week or two

4
      // e.g. duplicate chosenplace or all places

5
      // e.g. duplicate all participants or just some (complicated if participant duplicates)

6
      $newOwner = $user_id = Yii::$app->user->getId();
7
      // if user is participant - needs to reverse

8
      if ($this->owner_id == $user_id) {
9
        $addParticipant = false;
10
      } else {
11
        if (!isAttendee($this->id,$user_id)) {
12
          // user isn't owner or participant - error

13
          return false;
14
        } else {
15
          // reverse the owner and participant

16
          $addParticipant = $this->owner_id;
17
        }
18
      }
19
      // create new meeting - as copy of old meeting

20
      $m = new Meeting();
21
      $m->attributes = $this->attributes;
22
      $m->owner_id = $newOwner;
23
      $m->status = Meeting::STATUS_PLANNING;
24
      $m->created_at = time();
25
      $m->updated_at = time();
26
      $m->logged_at = 0;
27
      $m->cleared_at = 0;
28
      $m->sequence_id = 0;
29
      $m->save();
30
      // get prior meetings selected time and create two future times for the next two weeks

31
      $chosenTime=$this->getChosenTime($this->id);
32
      $mt1 = MeetingTime::createTimePlus($m->id,$m->owner_id,$chosenTime->start,$chosenTime->duration);
33
      $mt2 = MeetingTime::createTimePlus($m->id,$m->owner_id,$mt1->start,$chosenTime->duration);
34
      // clone the selected place (not all of them)

35
      $chosenPlace = $this->getChosenPlace($this->id);
36
      if ($chosenPlace!==false) {
37
        $mp = new MeetingPlace;
38
        $mp->suggested_by = $newOwner;
39
        $mp->attributes = $chosenPlace->attributes;
40
        $mp->meeting_id = $m->id;
41
        $mp->created_at = time();
42
        $mp->updated_at = time();
43
        $mp->save();
44
      }
45
      // clone the participants

46
      foreach ($this->participants as $p) {
47
        // skip if reschedule new owner was a participant

48
        if ($p->participant_id==$user_id) {
49
          continue;
50
        }
51
        // note Participant afterSave will create choices for place

52
        $clone_p = new Participant();
53
        $clone_p->attributes = $p->attributes;
54
        $clone_p->email = User::findOne($p->participant_id)->email;
55
        $clone_p->meeting_id = $m->id;
56
        $clone_p->status = Participant::STATUS_DEFAULT;
57
        $clone_p->created_at = time();
58
        $clone_p->updated_at = time();
59
        $clone_p->save();
60
      }
61
      // if participant asked to repeat

62
      // add the prior owner as a participant

63
      if ($addParticipant!==false) {
64
        $newP = new Participant();
65
        $newP->meeting_id = $m->id;
66
        $newP->participant_id = $addParticipant;
67
        $newP->invited_by = $user_id;
68
        $newP->status = Participant::STATUS_DEFAULT;
69
        $newP->created_at = time();
70
        $newP->updated_at = time();
71
        $newP->save();
72
      }
73
      MeetingLog::add($this->id,MeetingLog::ACTION_REPEAT,$user_id,0);
74
      return $m->id;
75
    }

MeetingTime::createTimePlus() dưới dây bổ sung một Meeting Time trong cùng ngày của tuần và giờ của ngày, nhưng một tuần trong tương lai, thậm chí nếu cuộc họp ban đầu đã diễn ra từ nhiều tháng trước. Vòng lặp while cần thiết cho những cuộc họp trước đây.

1
    public static function createTimePlus($meeting_id,$suggested_by,$start,$duration,$timeInFuture = 604800) {
2
      // finds time in multiples of a week or timeInFuture seconds ahead past the present time

3
      $newStart = $start+$timeInFuture;
4
      while ($newStart<time()) {
5
        $newStart+=$timeInFuture;
6
      }
7
      $mt = new MeetingTime();
8
      $mt->meeting_id = $meeting_id;
9
      $mt->start = $newStart;
10
      $mt->duration = $duration;
11
      $mt->end = $mt->start+($mt->duration*3600);
12
      $mt->suggested_by = $suggested_by;
13
      $mt->status = MeetingTime::STATUS_SUGGESTED;
14
      $mt->updated_at = $mt->created_at = time();
15
      $mt->save();
16
      return $mt;
17
    }

Gửi lại những lời mời

Tôi cũng xây dựng một tính năng gửi lại để phòng trường hợp người tham dự đã không nhận được lời mời ban đầu hoặc xác nhận sau cùng.

1
public static function resend($id) {
2
      $sender_id = Yii::$app->user->getId();
3
      // check if within resend limit

4
      $cnt = MeetingLog::find()
5
        ->where(['actor_id'=>$sender_id])
6
        ->andWhere(['meeting_id'=>$id])
7
        ->andWhere(['action'=>MeetingLog::ACTION_RESEND])
8
        ->count();
9
      if ($cnt >= Meeting::RESEND_LIMIT ) {
10
        return false;
11
      } else {
12
        $m = Meeting::findOne($id);
13
        if ($m->status == Meeting::STATUS_SENT) {
14
          $m->send($sender_id,true);
15
          // resend the planning invitation

16
        } else if ($m->status == Meeting::STATUS_CONFIRMED) {
17
          // resend the confirmed invitation

18
          $m->finalize($sender_id,true);
19
        }
20
        MeetingLog::add($id,MeetingLog::ACTION_RESEND,$sender_id,0);
21
        return true;
22
      }
23
    }

Tôi dự để xây dựng chức năng outbound cho email cho hoạt động không đồng bộ và làm cho việc tái truyền tải dễ dàng hơn, nhưng may mắn những phương thức hiện tại hoạt động tốt trong những tình huống gửi lại với vài thay đổi.

Lịch sử cuộc họp

Xuyên suốt loạt bài, chúng ta đã xây dựng một nhật ký những thay đổi cho những cuộc họp. Giờ có một cách cho những người tham gia xem lịch sử các cuộc họp. Nó trông như thế này.

Build Your Startup Advanced Scheduling - Viewing the meeting history of eventsBuild Your Startup Advanced Scheduling - Viewing the meeting history of eventsBuild Your Startup Advanced Scheduling - Viewing the meeting history of events

MeetingLogController.php kiểm tra rằng người xem là một người tham dự của buổi họp và chuẩn bị dữ liệu cho việc xem ghi chép.

1
public function actionView($id)
2
    {
3
      if (!Meeting::isAttendee($id,Yii::$app->user->getId())) {
4
        $this->redirect(['site/authfailure']);
5
      }
6
      $timezone = MiscHelpers::fetchUserTimezone(Yii::$app->user->getId());
7
      Yii::$app->timeZone = $timezone;
8
      	$searchModel = new MeetingLogSearch();
9
      $dataProvider = $searchModel->search(['MeetingLogSearch'=>['meeting_id'=>$id]]);
10
      $m= Meeting::findOne($id);
11
      return $this->render('index', [
12
          'searchModel' => $searchModel,
13
          'dataProvider' => $dataProvider,
14
          'meeting_id' => $id,
15
          'subject' => $m->getMeetingHeader('log'),
16
          'timezone' => $timezone,
17
      ]);
18
    }

Sau đó /frontend/views/meeting-log/index.php trình bày dữ liệu được hiển thị phía trên.

1
<?php Pjax::begin(); ?>
2
<div class="meeting-log-index">
3
    <h1><?php echo  Html::encode($this->title) ?></h1>
4
5
    <?php echo GridView::widget([
6
    'dataProvider' => $dataProvider,
7
    //'filterModel' => $searchModel,

8
    'columns' => [
9
        [
10
          'label'=>'Actor',
11
            'attribute' => 'actor_id',
12
            'format' => 'raw',
13
            'value' => function ($model) {
14
                    return '<div>'.MiscHelpers::getDisplayName($model->actor_id).'</div>';
15
                },
16
        ],
17
        [
18
          'label'=>'Action',
19
            'attribute' => 'action',
20
            'format' => 'raw',
21
            'value' => function ($model) {
22
                  return '<div>'.$model->getMeetingLogCommand().'</div>';
23
                },
24
        ],
25
        [
26
          'label'=>'Item',
27
            'attribute' => 'item_id',
28
            'format' => 'raw',
29
            'value' => function ($model) {
30
                        return '<div>'.$model->getMeetingLogItem().'</div>';
31
                },
32
        ],
33
        [
34
          'label'=>'Created',
35
            'attribute' => 'created_at',
36
            'format' => 'raw',
37
            'value' => function ($model) {
38
                        return '<div>'.Yii::$app->formatter->asDatetime($model->created_at,"hh:ss MMM d").'</div>';
39
                },
40
        ],
41
    ],
42
]);
43
?>
44
<?= Html::a(Yii::t('frontend', 'Return to Meeting'), ['meeting/view', 'id' => $meeting_id],
45
 ['class' => 'btn btn-primary  btn-info',
46
 'title'=>Yii::t('frontend','Return to meeting page'),
47
]); ?>
48
<?php Pjax::end(); ?>

Tiếp theo là gì?

Hiện giờ tôi đang ráo riết viết code để hoàn thành bản phát hành beta. Những người biên tập ở Envato Tuts+ đã cố gắng hết sức làm tôi mất tập trung với những con robot điều khiển tâm trí và những âm thanh OKCupid từ ứng dụng quy trình làm việc iOS của họ, nhưng họ đã không thành công. Phần phát triển của Meeting Planner tiếp tục với tốc độc nhanh.

Việc giải quyết kỹ thuật xã hội và UX cho những người tham dự họp để yêu cầu và đáp ứng các điều chỉnh nhỏ hơn của việc lập lịch hẹn có thể làm nên thương hiệu của Maeeting Planner hoặc phá vỡ nó, và đó là điều tôi đang chú tâm thực hiện nhất.

Nếu bạn vẫn chưa thực hiện, hãy lên kế hoạch cho cuộc họp đầu tiên của bạn với Meeting Planner.  Tôi đang lên kế hoạch để viết một bài hướng dẫn về crowdfunding, hãy tham khảo WeFunder Meeting Planner page. Bạn cũng có thể tìm tôi qua @reifman. Tôi luôn cởi mở với những ý tưởng tính năng mới và những gợi ý cho đề tài cho những bài hướng dẫn trong tương lai.

Hãy tiếp tục theo dõi nhiều bài hướng dẫn sắp tới qua loạt bài Xây Dựng Trang Khởi Nghiệp với PHP.

Những liên kết liên quan

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.