Advertisement
  1. Code
  2. PHP

Ang Paggawa ng Iyong Startup: Ang Paghiling ng Pagbabago sa Iskedyul

Scroll to top
Read Time: 18 min
This post is part of a series called Building Your Startup With PHP.
Building Your Startup: Advanced Scheduling Commands
Building Your Startup: Meetings With Multiple Participants

Tagalog (Wikang Tagalog) translation by Anna Nelson (you can also view the original English article)

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

Ang tutorial na ito ay bahagi ngserye tungkol sa pagbuo ng iyong startup gamit ang PHP sa Envato Tuts+. Sa seryeng ito, gagabayan ko kayo sa paglulunsad ng isang startup mula sa konsepto sa kasalukuyan gamit ang aking Meeting Planner app bilang isang makatotohanan na halimbawa.  Bawat hakbang sa kahabaan ng proseso, ibibigay ko ang open-source code ng aking Meeting Planner app bilang halimbawa kung saan maaari mong malaman ang pinagmulan. Tatalakayin ko din angmga isyu tungkol sa negosyo kung meron tayong madaanan.

Ang Pagbabago ng Iyong Planong Pagtitipon

Habang nagsisimula ang alpha testing phase ng Meeting Planner, makikita na ang pinakamalinaw na isyu sa feature ay ang kawalan ng kakayanan na baguhin ang pagtitipon matapos itong maiskedyul. Ito ay hindi madaling problema. Maaari bang baguhin ang pagtitipon ng walang permiso ng kalahok?  O dapat kang humingi ng permiso? O gawin ang kahit ano sa dalawang iyon, depende sa iyong tungkulin sa pag-aayos ng pagtitipon? Paano kung nais mo lang tanungin kung maaaring magtipon makalipas ang 15 minuto mula sa orihinal na oras ng pagtitipon – dapat madali lang itong gawin, tama ba?

Ang paglutas ng lahat ng ito ay nangangailangan ng panlipunang aspeto ng pagbabago ng pagtitipon.

Sa paglipas ng panahon, naunawaan ko na ang kakayanan sa pagsasaayos ng mga pagtitipon nang madali matapos itong maitakda ay maaaring ikaganda o ikasira ng tatak ng Meeting Planner.

Sa huling episode tungkol sa Advanced Scheduling, ipinatupad ko ang Make Changes, kung saan ang nag-organisa o ang kalahok ay binibigyan ng permiso na baguhin ang itinakdang pagtitipon ng hindi humihingi ng pahintulot. Sa tutoryal ngayon, ipapakita ko sa inyo ang paggawa ng Imprastraktura ng Request Changes. Ito ay nangangailangan ng request change(s) ng mga kalahok at maaaring ang iba ay tanggapin o tanggihan sila, na makakaapekto sa huling detalye ng kalendaryo ng pagtitipon.

Habang kayo ay nagbabasa, sana subukan niyo ang bagong “request a change” na feature sa live site at ibahagi ang inyong ideya at feedback sa komento sa ibaba. Ako ay sumasagot sa talakayan sa ibaba, at maaari din kayong makipag-ugnayan sa akin sa @reifman sa Twitter. Ako ay laging bukas sa mga bagong tampok na mga ideya para sa Meeting Planner pati na rin sa mga mungkahi para sa mga susunod na episode ng mga serye.

Bilang paalala, ang lahat ng mga code para sa Meeting Planner ay nakasulat sa Yii2 Framework para sa PHP Kung gusto mong matuto nang iba pa tungkol sa Yii2, tingnan ang aming parallel series Programming Sa Yii2.

Magsimula na tayo.

Ang Paggawa ng Request Changes

Isang Mataas na Bundok na Aakyatin

Bukod sa meeting view at mga scheduling feature, ang Request Changes ay nangangailangan ng mas maraming oras at bagong kowd kaysa sa iba pang mga feature sa proyektong ito.

Gaya ng nabanggit ko sa huling episode, ang paglalagay ng kowd sa lahat ng may pangunahing seguridad ay nangangailangan ng mas mahabang oras kaysa sa mabilis na prototyping, habang ang pagdidisenyo at paggawa ng feature naman na ito ay gumagamit ng napakaraming mga platform area:

  • Ang pagdidisenyo sa pamamagitan ng social engineering sa paghiling at paggawa ng pagbabago sa iskedyul.
  • Ang pananatiling simple ng UX sa paghingi ng pagbabago ay nakakatulong sa mga taong humiling at tumugon sa mga change request ng hindi nagugulo ang interface.
  • Ang pangangasiwa ng mga hiling para sa 1:1 na mga pagtitipon ay magiging madali, habang ang kowding para sa darating na multiple participants feature ay nangangailangan ng mas sopistikadong imprastraktura.
  • Ang pangangasiwa ng mga tugon sa mga request na may maraming kalahok.
  • Ang mga Email notification ng bago at mga kinanselang request, ang mga tinanggap at tinanggihan na mga tugon.
  • Ang pagbago ng kumpirmasyon ng pagtitipon at ang mga detalye ng kalendaryo kung ang mga tinanggap na request ay nakakaapekto ng iskedyul.

Kaya habang ang feature na ito ay hindi isang perpektong larawan ng pagbabago, narito ang mga screenshot ng eventual production server code pull.

Narito ang mga pagbabago sa umiiral na kowd:

Build Your Startup Request Scheduling Changes - Git Pull File ChangesBuild Your Startup Request Scheduling Changes - Git Pull File ChangesBuild Your Startup Request Scheduling Changes - Git Pull File Changes

At narito ang mga bagong file:

Build Your Startup Request Scheduling Changes - Git Pull New FilesBuild Your Startup Request Scheduling Changes - Git Pull New FilesBuild Your Startup Request Scheduling Changes - Git Pull New Files

Maraming bagong kowd ang kasama sa feature na ito.

Ang mga Table at ang mga Migration nito

Sa huli, nagpasya akong gumawa ng arkitekturang nakapaloob sa dalawang table. Ang una ay ang Requests:

1
$this->createTable('{{%request}}', [
2
    'id' => Schema::TYPE_PK,
3
    'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL DEFAULT 0',
4
    'requestor_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
5
    'completed_by' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
6
    'time_adjustment' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
7
    'alternate_time' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
8
    'meeting_time_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
9
    'place_adjustment' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
10
    'meeting_place_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
11
    'note' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""',
12
    'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
13
    'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
14
    'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
15
], $tableOptions);
16
$this->addForeignKey('fk_request_meeting', '{{%request}}', 'meeting_id', '{{%meeting}}', 'id', 'CASCADE', 'CASCADE');
17
$this->addForeignKey('fk_request_user', '{{%request}}', 'requestor_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');

Narito ang mga hindi nagbabagong elemento na magpapaliwanag sa modelo nang mas malalim:

1
  const STATUS_OPEN = 0;
2
  const STATUS_ACCEPTED = 10;
3
  const STATUS_REJECTED = 20;
4
  const STATUS_WITHDRAWN = 30;
5
6
  const TIME_ADJUST_NONE = 50;
7
  const TIME_ADJUST_ABIT = 60;
8
  const TIME_ADJUST_OTHER = 70;
9
10
  const PLACE_ADJUST_NONE = 80;
11
  const PLACE_ADJUST_OTHER = 90;

May dalawang paraan para baguhin ang oras: TIME_ADJUST_ABIT, i.e. pagitan ng mga minuto o mga oras ng mas maaga o mas huli sa napiling oras, o TIME_ADJUST_OTHER, o ibang oras ng pagtitipon.

At ang ikalawang table ay ang RequestResponses:

1
$this->createTable('{{%request_response}}', [
2
    'id' => Schema::TYPE_PK,
3
    'request_id' => Schema::TYPE_INTEGER.' NOT NULL DEFAULT 0',
4
    'responder_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0',
5
    'note' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""',
6
    'response' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
7
    'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
8
    'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
9
], $tableOptions);

Ang ibig sabihin nito, kung sino ang humiling ng pagbabago, kung sino ang tumugon dito at kung ano ang tugon dito: accept o decline.

Ang pangalawang table ay kailangan para sa maramihang participant environment.

Ang Paghiling ng Pagbabago

Ang magtitipon na mga organizer at mga kahalok ay maaaring magkaroon ng access sa Request Changes sa pamamagitan ng dropdown Options menu na aming ginawa sa huling episode:

Build Your Startup Request Scheduling Changes - Options Menu Request ChangesBuild Your Startup Request Scheduling Changes - Options Menu Request ChangesBuild Your Startup Request Scheduling Changes - Options Menu Request Changes

Ang Request Change Form

Ang RequestController.php's actionCreate() ay nagpapakita ng form kung saan nagmumula ang mga request change ng isang user.

Build Your Startup Request Scheduling Changes - Request a Change FormBuild Your Startup Request Scheduling Changes - Request a Change FormBuild Your Startup Request Scheduling Changes - Request a Change Form

At dito nagsisimula ang kumplikasyon. Ano ang mga uri ng pagbabago na maaaring hilingin ng mga kalahok?

  • Gusto mo bang magtipon ng mas maaga o mamaya pa?              
  • Gusto mo bang magtipon sa ng ibang oras?
  • Gusto mo bang magtipon sa ibang lugar?

Tandaan: Hindi ko pa ipinapatupad ang kakayanan ng paglalagay ng mga bagong lugar at oras – kasalukuyan,  maaari kang mamili ng bagong petsa at lugar mula sa mga ibinigay na opsyon noong panahon ng pagpaplano.

Ang Dropdown ng Earlier at Later Times

Ang kowd para lumikha ng dropdown list ay masalimuot. Ginawa ko ito upang makapili kayo ng mas maaga ng dalawa at kalahating oras o mas huli, na may 15-minutong pagitan na malapit sa orihinal na oras at 30-minutong pagitan pagkatapos nito:

1
for ($i=1;$i<12;$i++) {
2
  // later times

3
  if ($i<4 || $i%2 == 0) {
4
    $altTimesList[$chosenTime->start+($i*15*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($i*15*60),$timezone,false);
5
  }
6
  // earlier times

7
  $earlierIndex = ((12-$i)*-15);
8
  if ($i%2 == 0 || $i>=9) {
9
    $altTimesList[$chosenTime->start+($earlierIndex*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($earlierIndex*60),$timezone,false);
10
  }
11
}
12
$altTimesList[$chosenTime->start]='────────────────────';
13
$altTimesList[-1000]=Yii::t('frontend','Select an alternate time below');
14
ksort($altTimesList);

Pinuno ko ang $altTimesList ng mga key ng bawat posibleng oras na akma sa time zone ng user. At ginamit ko ang ksort() para ibukod ang dropdown ng mga naunang oras upang makita ang mga ito bago ang mga huling oras.

Isa sa mga tagapayo ng Meeting Planner (mayroon lang akong isa sa mga sandaling ito), ay iminungkahing ipakita ang kasalakuyang napiling oras ng pagtitipon, na ginawa ko sa ibaba. Ako din ay naglagay ng separator na may opsyon na hindi gumana sa dropdown. Ito ay hinihiwalay ang mas maagang oras mula sa huling oras ngunit ito ay di maaaring piliin:

Build Your Startup Request Scheduling Changes - Enhanced Request Form with SeparatorBuild Your Startup Request Scheduling Changes - Enhanced Request Form with SeparatorBuild Your Startup Request Scheduling Changes - Enhanced Request Form with Separator

Narito ang kowd ng dropdown, na nagpapakita kung paano di gagana ang separator batay sa $currentStart index key nito:

1
<?php
2
echo $form->field($model, 'alternate_time')->label(Yii::t('frontend','Choose a time slightly earlier or later than {currentStartStr}',['currentStartStr'=>$currentStartStr]))
3
  ->dropDownList(
4
      $altTimesList,
5
      ['options' => [$currentStart => ['disabled' => true]]]
6
  );
7
  ?>

At kung ang mga kalahok ay nais pumili ng ibang oras, mayroong JQuery para palitan ang mga dropdown, isa pang kumplikado sa paggawa ng mga form:

1
    <?php ActiveForm::end();
2
    $this->registerJsFile(MiscHelpers::buildUrl().'/js/request.js',['depends' => [\yii\web\JqueryAsset::className()]]);
3
    ?>
4
</div>

Narito ang /frontend/web/js/request.js:

1
  $("#adjust_how" ).change(function() {
2
    if ($("#adjust_how" ).val()==50) {
3
      $("#choose_earlier").addClass('hidden');
4
      $("#choose_another").addClass('hidden');
5
    } else if ($("#adjust_how" ).val()==60) {
6
      $("#choose_earlier").removeClass('hidden');
7
      $("#choose_another").addClass('hidden');
8
    } else {
9
      $("#choose_earlier").addClass('hidden');
10
      $("#choose_another").removeClass('hidden');
11
    }
12
  });

Narito ang anyo ng form na may mga nakatagong alternatibong oras:

Build Your Startup Request Scheduling Changes - Selecting a different placeBuild Your Startup Request Scheduling Changes - Selecting a different placeBuild Your Startup Request Scheduling Changes - Selecting a different place

Ang iba’t-ibang lugar ay pinagsama-sama sa iisang dropdown list ng mga lugar (tulad ng nakikita niyo sa itaas, na itinatampok sa larawan).

Ang Pag-aayos ng Request

Matapos magawa ang request, ipinababatid namin sa requestor na ang request ay ipinapaalam sa iba pang kalahok ng pagtitipon.  At sa tuwing may mga aktibong request para sa pagtitipon, mayroon link para makita ang mga ito:

Build Your Startup Request Scheduling Changes - Meeting page View RequestsBuild Your Startup Request Scheduling Changes - Meeting page View RequestsBuild Your Startup Request Scheduling Changes - Meeting page View Requests

Napagpasiyahan ko na ito ay magiging isang simple at maayos na paraan para ma-access ang mga request. 

Ang Listahan ng mga Meeting Request

Narito ang listahan ng mga request para sa isang pagtitipon, na kadalasan ay iisa lang:

Build Your Startup Request Scheduling Changes - List of Meeting RequestsBuild Your Startup Request Scheduling Changes - List of Meeting RequestsBuild Your Startup Request Scheduling Changes - List of Meeting Requests

Ang Request::buildSubject() ay lumilikha ng string sa itaas na batay sa nilalaman ng request, i.e. pagbabago ng oras at/o lugar:

1
public static function buildSubject($request_id,$include_requestor = true) {
2
  $r = Request::findOne($request_id);
3
  $requestor = MiscHelpers::getDisplayName($r->requestor_id);
4
  $timezone = MiscHelpers::fetchUserTimezone(Yii::$app->user->getId());
5
  $rtime ='';
6
  $place = '';
7
  switch ($r->time_adjustment) {
8
    case Request::TIME_ADJUST_NONE:
9
    break;
10
    case Request::TIME_ADJUST_ABIT:
11
      $rtime = Meeting::friendlyDateFromTimestamp($r->alternate_time,$timezone);
12
    break;
13
    case Request::TIME_ADJUST_OTHER:
14
      $t = MeetingTime::findOne($r->meeting_time_id);
15
      if (!is_null($t)) {
16
          $rtime = Meeting::friendlyDateFromTimestamp($t->start,$timezone);;
17
      }
18
    break;
19
  }
20
  if ($r->place_adjustment == Request::PLACE_ADJUST_NONE || $r->place_adjustment == 0 && $r->meeting_place_id ==0 ) {
21
    // do nothing

22
  } else {
23
    // get place name

24
    $place = MeetingPlace::findOne($r->meeting_place_id)->place->name;
25
  }
26
  $result = $requestor.Yii::t('frontend',' asked to meet at ');
27
  if ($rtime=='' && $place =='') {
28
    $result.=Yii::t('frontend','oops...no changes were requested.');
29
  } else if ($rtime<>'') {
30
    $result.=$rtime;
31
    if ($place<>'') {
32
      $result.=Yii::t('frontend',' and ');
33
    }
34
  }
35
  if ($place<>'') {
36
    $result.=$place;
37
  }
38
  return $result;
39
}

Ang function na ito ay paulit-ulit din na ginagamit sa mga email notification.

Mayroon din na mga limitasyon ang RequestController.php kung saan ang mga gumagamit ay hindi pinahihintulutang humiling ng higit pa sa isang request kada pagtitipon:

1
public function actionCreate($meeting_id)
2
    {
3
        // verify is attendee

4
        if (!Meeting::isAttendee($meeting_id,Yii::$app->user->getId())) {
5
          $this->redirect(['site/authfailure']);
6
        }
7
        if (Request::countRequestorOpen($meeting_id,Yii::$app->user->getId())>0) {
8
            $r = Request::find()
9
              ->where(['meeting_id'=>$meeting_id])
10
              ->andWhere(['requestor_id'=>Yii::$app->user->getId()])
11
              ->andWhere(['status'=>Request::STATUS_OPEN])
12
              ->one();
13
            Yii::$app->getSession()->setFlash('info', Yii::t('frontend','You already have an existing request below.'));
14
              return $this->redirect(['view','id'=>$r->id]);
15
        }
16
    

Narito ang view request page na nagpapakita ng limitasyon:

Build Your Startup Request Scheduling Changes - Viewing a RequestBuild Your Startup Request Scheduling Changes - Viewing a RequestBuild Your Startup Request Scheduling Changes - Viewing a Request

Kung ito ay sarili mong request, maaari mong Kanselahin ang Iyong Request.

Tulad ng nakikita mo, napakaraming iba’t-ibang UX functionality para makagawa nito. At hindi ko pa ipinapakita sa inyo kung paano tumugon ang ibang tao maliban sa requestor.

Ang Request at Response Notification Emails

Sa proseso ng paggawa ng mga feature na ito, napagpasiyahan kong lumikha ng generic_html at mga generic_text email template pati na rin ang isang function na maaaring gamitin muli, ang Request::notify(). Ito ay ginagamit upang madaling maihatid ang iba’t-ibang uri ng mga anunsyo sa loob ng Meeting Planner.

Narito ang paraan ng Request::create() para sa paghahanda ng email

1
public function create() {
2
  $user_id = $this->requestor_id;
3
  $meeting_id = $this->meeting_id;
4
  $subject = Request::buildSubject($this->id);
5
  $content=[
6
    'subject' => Yii::t('frontend','Change Requested to Your Meeting'),
7
    'heading' => Yii::t('frontend','Requested Change to Your Meeting'),
8
    'p1' => $subject,
9
    'p2' => $this->note,
10
    'plain_text' => $subject.' '.$this->note.'...'.Yii::t('frontend','Respond to the request by visiting this link: '),
11
  ];
12
  $button= [
13
    'text' => Yii::t('frontend','Respond to Request'),
14
    'command' => Meeting::COMMAND_VIEW_REQUEST,
15
    'obj_id' => $this->id,
16
  ];
17
  $this->notify($user_id,$meeting_id, $content,$button);
18
  // add to log

19
  MeetingLog::add($meeting_id,MeetingLog::ACTION_REQUEST_SENT,$user_id,0);
20
}

Ang $content array ay awtomatikong lumalabas para sa subject ng email, heading ng message at talata, habang ang $button array ay ginagamit para sa anumang command button tulad ng Respond to Request o View Meeting.

Narito ang paraan ng notify(), na katulad sa earlier send() at finalize() actions na nagpapadala ng email:

1
public static function notify($user_id,$meeting_id,$content,$button = false) {
2
  // sends a generic message based on arguments

3
  $mtg = Meeting::findOne($meeting_id);
4
  // build an attendees array for all participants without contact information

5
  $cnt =0;
6
  $attendees = array();
7
  foreach ($mtg->participants as $p) {
8
      $auth_key=\common\models\User::find()->where(['id'=>$p->participant_id])->one()->auth_key;
9
      $attendees[$cnt]=['user_id'=>$p->participant_id,'auth_key'=>$auth_key,
10
      'email'=>$p->participant->email,
11
      'username'=>$p->participant->username];
12
      $cnt+=1;
13
  }
14
  // add organizer

15
  $auth_key=\common\models\User::find()->where(['id'=>$mtg->owner_id])->one()->auth_key;
16
  $attendees[$cnt]=['user_id'=>$mtg->owner_id,'auth_key'=>$auth_key,
17
    'email'=>$mtg->owner->email,
18
    'username'=>$mtg->owner->username];
19
// use this code to send

20
foreach ($attendees as $cnt=>$a) {
21
  // check if email is okay and okay from this sender_id

22
  if ($user_id != $a['user_id'] && User::checkEmailDelivery($a['user_id'],$user_id)) {
23
    Yii::$app->timeZone = $timezone = MiscHelpers::fetchUserTimezone($a['user_id']);
24
      // Build the absolute links to the meeting and commands

25
      $links=[
26
        'home'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a['user_id'],$a['auth_key']),
27
        'view'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a['user_id'],$a['auth_key']),
28
        'footer_email'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a['user_id'],$a['auth_key']),
29
        'footer_block'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,$user_id,$a['user_id'],$a['auth_key']),
30
        'footer_block_all'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a['user_id'],$a['auth_key']),
31
      ];
32
      if ($button!==false) {
33
        $links['button_url']=MiscHelpers::buildCommand($mtg->id,$button['command'],$button['obj_id'],$a['user_id'],$a['auth_key']);
34
        $content['button_text']=$button['text'];
35
      }
36
      // send the message

37
      $message = Yii::$app->mailer->compose([
38
        'html' => 'generic-html',
39
        'text' => 'generic-text'
40
      ],
41
      [
42
        'meeting_id' => $mtg->id,
43
        'sender_id'=> $user_id,
44
        'user_id' => $a['user_id'],
45
        'auth_key' => $a['auth_key'],
46
        'links' => $links,
47
        'content'=>$content,
48
        'meetingSettings' => $mtg->meetingSettings,
49
    ]);
50
      // to do - add full name

51
    $message->setFrom(array('support@meetingplanner.com'=>$mtg->owner->email));
52
    $message->setReplyTo('mp_'.$mtg->id.'@meetingplanner.io');
53
    $message->setTo($a['email'])
54
        ->setSubject($content['subject'])
55
        ->send();
56
    }
57
  }
58
}

Ang generic_html.php layout ay batay sa simpleng textual update template na aking tinalakay sa mga tutoryal tungkol sa email template. Ito ay nagbibigay ng isang mahusay na format upang maipaalam sa mga kalahok sa pamamagitan ng email gamit ang ilang mga talata.

Narito ang generic_html.php view file na pinagsamang $content at $button data.  Ito ay sumusuri sa ikalawa at ikatlong talata, e.g. $p2, $p3 at $button data:

1
<tr>
2
  <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:10px 60px 0; width:100%" align="center" width="100%">
3
    <p>Hi <?php echo Html::encode(MiscHelpers::getDisplayName($user_id)); ?>,</p>
4
    <p><?= Html::encode($content['p1']) ?></p>
5
    <?php if ($content['p2']<>'') {
6
    ?>
7
    <p><?= Html::encode($content['p2']); ?></p>
8
    <?php
9
      }
10
    ?>
11
    <?php if (isset($content['p3']) && $content['p3']<>'') {
12
    ?>
13
    <p><?= Html::encode($content['p3']); ?></p>
14
    <?php
15
      }
16
    ?>
17
  </td>
18
</tr>
19
<?php if ($links['button_url']!='') {
20
 ?>
21
<tr>
22
  <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:30px 0 30px 0" align="center">
23
    <div>
24
<!--[if mso]>

25
      <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="http://" style="height:45px;v-text-anchor:middle;width:155px;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">

26
        <w:anchorlock/>

27
        <center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">My Account</center>

28
      </v:roundrect>

29
    <![endif]--><a href="<?php echo $links['button_url'] ?>" style='color:#fff; text-decoration:none; -webkit-text-size-adjust:none; background-color:#ff6f6f; border-radius:5px; display:inline-block; font-family:"Cabin", Helvetica, Arial, sans-serif; font-size:14px; font-weight:regular; line-height:45px; mso-hide:all; text-align:center; width:155px' bgcolor="#ff6f6f" align="center" width="155"><?= Html::encode($content['button_text']); ?></a>
30
  </div>
31
  </td>
32
</tr>
33
<?php } ?>

Narito ang halimbawa ng isang notipikasyon na hiniling ni Rob Smith sa akin upang palitan ang oras at lugar ng aming pagtitipon (na nagmula sa kowd sa itaas):

Build Your Startup Request Scheduling Changes - Email Notification of Requested ChangeBuild Your Startup Request Scheduling Changes - Email Notification of Requested ChangeBuild Your Startup Request Scheduling Changes - Email Notification of Requested Change

Ang Tumugon sa Kahilingan

Kapag pinindot ko ang Respond to Request, ako ay mapupunta sa RequestResponse Controller's actionCreate() method:

Build Your Startup Request Scheduling Changes - Respond to Request Form - Accept or DeclineBuild Your Startup Request Scheduling Changes - Respond to Request Form - Accept or DeclineBuild Your Startup Request Scheduling Changes - Respond to Request Form - Accept or Decline

Sa buong panahon ng request UX, isinama ko ang kakayanan ng mga tao na sumulat ng mga tala na magbibigay ng konteksto para sa mga hiling at mga tugon.

Ang isang hamon ng form na ito ay tukuyin kung paano mangangasiwa ng mga tugon sa iba’t-ibang mga controller method batay sa submit button na pinindot. Sa madaling salita, ang pagkakakilanlan sa pagitan ng iba’t-ibang mga submit POST button click.

1
public function actionCreate($id)
2
{
3
  $request = Request::findOne($id);
4
  if (!Meeting::isAttendee($request->meeting_id,Yii::$app->user->getId())) {
5
    $this->redirect(['site/authfailure']);
6
  }
7
    // has this user already responded

8
    $check = RequestResponse::find()
9
      ->where(['request_id'=>$id])
10
      ->andWhere(['responder_id'=>Yii::$app->user->getId()])
11
      ->count();
12
    if ($check>0) {
13
      Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you already responded to this request.'));
14
      return $this->redirect(['meeting/view', 'id' => $request->meeting_id]);
15
    }
16
    if ($request->requestor_id == Yii::$app->user->getId()) {
17
      Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, can not respond to your own request.'));
18
      return $this->redirect(['meeting/view', 'id' => $request->meeting_id]);
19
    }
20
    $subject = Request::buildSubject($id);
21
    $model = new RequestResponse();
22
    $model->request_id = $id;
23
    $model->responder_id = Yii::$app->user->getId();
24
    if ($model->load(Yii::$app->request->post()) ) {
25
      $posted = Yii::$app->request->post();
26
      if (isset($posted['accept'])) {
27
        // accept

28
        $model->response = RequestResponse::RESPONSE_ACCEPT;
29
        $model->save();
30
        $request->accept($model);
31
        Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Request accepted. We will update the meeting details and inform other participants.'));
32
      } else {
33
        // reject

34
        $model->response = RequestResponse::RESPONSE_REJECT;
35
        $model->save();
36
        $request->reject($model);
37
        Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Your decline has been recorded. We will let other participants know.'));
38
      }
39
      return $this->redirect(['/meeting/view', 'id' => $request->meeting_id]);
40
    } else {
41
        return $this->render('create', [
42
            'model' => $model,
43
            'subject' => $subject,
44
            'meeting_id' => $request->meeting_id,
45
        ]);
46
    }
47
}

Narito ang /frontend/views/request-response/_form.php:

1
<div class="request-response-form">
2
    <?php $form = ActiveForm::begin(); ?>
3
      <?= BaseHtml::activeHiddenInput($model, 'responder_id'); ?>
4
        <?= BaseHtml::activeHiddenInput($model, 'request_id'); ?>
5
    <?= $form->field($model, 'note')->label(Yii::t('frontend','Include a note'))->textarea(['rows' => 6])->hint(Yii::t('frontend','optional')) ?>
6
7
<div class="form-group">
8
  <?= Html::submitButton(Yii::t('frontend', 'Accept Request'), 
9
        ['class' => 'btn btn-success','name'=>'accept',]) ?>
10
  <?= Html::submitButton(Yii::t('frontend', 'Decline Request'),
11
        ['class' => 'btn btn-danger','name'=>'reject',
12
      'data' => [
13
          'confirm' => Yii::t('frontend', 'Are you sure you want to decline this request?'),
14
          'method' => 'post',
15
      ],]) ?>

Lubhang kailangang maglagay ako ng name values na 'accept' o 'reject' sa bawat pindutan. Pagkatapos, ito ay ipinapadala bilang posted value tulad ng ipinapakita:

1
    if ($model->load(Yii::$app->request->post()) ) {
2
      $posted = Yii::$app->request->post();
3
      if (isset($posted['accept'])) {
4
        // accept

5
        $model->response = RequestResponse::RESPONSE_ACCEPT;
6
        $model->save();
7
        $request->accept($model);
8
        Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Request accepted. We will update the meeting details and inform other participants.'));
9
      } else {
10
        // reject

11
        $model->response = RequestResponse::RESPONSE_REJECT;
12
        $model->save();
13
        $request->reject($model);
14
        Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Your decline has been recorded. We will let other participants know.'));
15
      }
16
      return $this->redirect(['/meeting/view', 'id' => $request->meeting_id]);
17
    }

Kung ang taga-tugon ay tinanggap o tinanggihan ang request, makakakita sila ng isang flash message at sila ay padadalhan ng email. At ang pagtitipon ay hindi na magpapakita ng anumang aktibong mga request:

Build Your Startup Request Scheduling Changes - Meeting page after Request accepted Build Your Startup Request Scheduling Changes - Meeting page after Request accepted Build Your Startup Request Scheduling Changes - Meeting page after Request accepted

Narito ang Requested Change Accepted notification email:

Build Your Startup Request Scheduling Changes - Email notification of requested change being acceptedBuild Your Startup Request Scheduling Changes - Email notification of requested change being acceptedBuild Your Startup Request Scheduling Changes - Email notification of requested change being accepted

Marami ang nagaganap sa Request::accept() na nasa ibaba:

1
public function accept($request_response) {
2
  // to do - this will need to change when there are multiple participants

3
  $this->status = Request::STATUS_ACCEPTED;
4
  $this->update();
5
  $m = Meeting::findOne($this->meeting_id);
6
  // is there a new time

7
  switch ($this->time_adjustment) {
8
    case Request::TIME_ADJUST_ABIT:
9
      // create a new meeting time with alternate_time

10
      $this->meeting_time_id = MeetingTime::addFromRequest($this->id);
11
      $this->update();
12
      // mark as selected

13
      MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id);
14
    break;
15
    case Request::TIME_ADJUST_OTHER:
16
     // mark as selected

17
      MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id);
18
    break;
19
  }
20
  // is there a different place

21
  if ($this->place_adjustment == Request::PLACE_ADJUST_OTHER || $this->meeting_place_id !=0 ) {
22
    MeetingPlace::setChoice($this->meeting_id,$this->meeting_place_id,$request_response->responder_id);
23
  }
24
  if ($m->isOwner($request_response->responder_id)) {
25
    // they are an organizer

26
    $this->completed_by =$request_response->responder_id;
27
    $this->update();
28
    MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ORGANIZER_ACCEPT,$request_response->responder_id,$this->id);
29
  } else {
30
    // they are a participant

31
    MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ACCEPT,$request_response->responder_id,$this->id);
32
  }
33
  $user_id = $request_response->responder_id;
34
  $subject = Request::buildSubject($this->id, true);
35
  $p1 = MiscHelpers::getDisplayName($user_id).Yii::t('frontend',' accepted the request: ').$subject;
36
  $p2 = $request_response->note;
37
  $p3 = Yii::t('frontend','You will receive an updated meeting confirmation reflecting these change(s). It will also include an updated attachment for your Calendar.');
38
  $content=[
39
    'subject' => Yii::t('frontend','Accepted Requested Change to Meeting'),
40
    'heading' => Yii::t('frontend','Requested Change Accepted'),
41
    'p1' => $p1,
42
    'p2' => $p2,
43
    'p3' => $p3,
44
    'plain_text' => $p1.' '.$p2.' '.$p3.'...'.Yii::t('frontend','View the meeting here: '),
45
  ];
46
  $button= [
47
    'text' => Yii::t('frontend','View the Meeting'),
48
    'command' => Meeting::COMMAND_VIEW,
49
    'obj_id' => 0,
50
  ];
51
  $this->notify($user_id,$this->meeting_id, $content,$button);
52
  // Make changes to the Meeting

53
  $m->increaseSequence();
54
  // resend the finalization - which also needs to be done for resend invitation

55
  $m->finalize($m->owner_id);
56
}

Bago ipadala ang email, ang iskedyul ng pagtitipon ay magbabago upang ipakita ang bagong petsa/oras at/o ang bagong lugar.  Pagkatapos ipadala ang email, ang pagtitipon ay aayusin. Ito ay magpapadala ng bagong update tungkol sa pagtitipon na may kasamang updated calendar file sa lahat ng mga kalahok:

Build Your Startup Request Scheduling Changes - Updating Meeting NoticeBuild Your Startup Request Scheduling Changes - Updating Meeting NoticeBuild Your Startup Request Scheduling Changes - Updating Meeting Notice

Ano ang Susunod?

Sana ay nasiyahan kayo sa tutoryal na ito.  Ang paggawa ng feature na ito ay natapos nang mas matagal sa aking inaasahan ngunit maganda naman ang kinalabasan. Sa tingin ko, ito ay nagdagdag ng elemento sa paggawa ng iskedyul gamit ang Meeting Planner na hindi mapapantayan ng ibang mga serbisyo.

Kung hindi mo pa nasusubukan, halina’t gumawa ng iskedyul ng iyong unang pagtitipon sa pamamagitan ng Meeting Planner. Patuloy pa din akong gumagawa ng hindi kapani-kapaniwalang progresibo sa beta release, kahit na maraming bagay ang nakakagambala (ang kowding ay mahirap):

Ako din ay nagpaplanong gumawa ng tutoryal tungkol sa crowdfunding, kaya pakiusap sundan at bisitahin ang aming WeFunder Meeting Planner page.

Mangyaring ibahagi ang inyong mga komento sa ibaba. Ako ay laging bukas sa bagong mga tampok na ideya at mga paksa sa mga susunod na tutoryal. Maaari niyo din akong maabot sa @reifman.

Manatiling nakatutok sa lahat ng ito at sa iba pang mga darating na tutoryal sa pamamagitan ng panonood ng Building Your Startup With PHP series.  At siguraduhing panoorin ang aming Programming ng Yii2 Series (Envato Tuts+).

Related Links

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.