Ce tutoriel fait partie de la Construire votre démarrage avec la série PHP sur Envato Tuts +. Dans cette série, je vous guide dans le lancement d’une startup du concept à la réalité en utilisant mes Planificateur de réunion application comme exemple de la vie réelle. À chaque étape du processus, je publierai le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Je traiterai également les problèmes liés au démarrage au fur et à mesure qu'ils surviennent.
Au début de la phase de test de Meeting Planner alpha, la principale lacune était l'incapacité de modifier une réunion après sa planification. Mais il manquait également d'autres fonctionnalités, telles que le renvoi d'une invitation perdue dans un courrier électronique, le replanification complète d'une réunion ou la possibilité d'afficher et de modifier les paramètres de contrôle choisis par l'organisateur..
Fait intéressant, j'ai aussi commencé à réaliser que la capacité d'ajuster facilement les réunions après leur planification pouvait faire ou défaire la marque Meeting Planner. Par exemple, il y a beaucoup d'ingénierie sociale dans l'ajustement de réunion post-planification. Souvent, vous devez demander au (x) participant (s) s’il est correct d’ajuster l’heure ou le lieu. Peut-être que vous voulez juste vous réunir 15 minutes plus tôt ou le lendemain au même endroit et à la même heure. Mais vous ne pouvez pas toujours faire ces changements sans consentement.
Garder le site facile à comprendre et simple à utiliser avec toutes ces fonctionnalités est ma principale directive. Comment puis-je ajouter un nombre croissant de fonctionnalités sans encombrer l'interface utilisateur ni trop la dissimuler? Comment cela fonctionnerait-il sur les interfaces mobiles et de bureau??
Dans le tutoriel d'aujourd'hui, je couvrirai l'agrandissement de la barre de navigation à l'aide de Bootstrap et les bases de la création de certaines fonctionnalités de planification avancées dans Meeting Planner. La semaine prochaine, je passerai en revue la création d'une fonctionnalité plus complexe permettant aux participants de demander des modifications et à d'autres de les accepter ou de les refuser..
J'espère que vous allez essayer toutes les nouvelles fonctionnalités de planification sur le site en direct et partager vos impressions et commentaires dans les commentaires ci-dessous. Je participe aux discussions, mais vous pouvez également me joindre à @reifman sur Twitter. Je suis toujours ouvert aux nouvelles idées de fonctionnalités de Meeting Planner ainsi qu'aux suggestions pour les futurs épisodes de la série..
Pour rappel, tout le code de Meeting Planner est fourni en open source et écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez mes séries parallèles Programmer avec Yii2.
Voyons d’abord comment développer la barre de navigation existante. Voici une liste des fonctionnalités que j'avais prévu d'ajouter:
Comme vous pouvez le constater, non seulement il y avait beaucoup de fonctionnalités à créer, mais je ne savais pas non plus clairement où les placer dans l'interface utilisateur sans créer de désordre..
La barre de commande devait également être modifiée en fonction du statut d'une réunion. Les réunions en cours de planification avaient des options différentes de celles en attente, confirmées ou complétées.
Mon idée initiale était de fournir un petit Réglages avancés lien qui afficherait une barre de commande cachée. J'ai expérimenté cela au début, mais ce n'était pas esthétique.
Ensuite, j'ai examiné la documentation Bootstrap et trouvé la liste déroulante déroulante:
J'ai aimé la façon dont cela fonctionnait. J'ai donc décidé de placer la plupart des commandes avancées dans un bouton déroulant orienté vers la gauche..
Voici un exemple de ce à quoi cela ressemble lors de la phase de planification d’une réunion:
Notez que bootstrap offre une classe pour les menus déroulants. Une barre de commande située au bas de la page utilise le menu déroulant comme suit:
/ * la position partielle sait paramétrer la variable dropclass vers le haut ou le bas * / echo $ this-> render ('_ command_bar_past', ['modèle' => $ modèle, 'isPast' => vrai, 'dropclass' => 'dropup ',' isOwner '=> $ isOwner,]); / * la vue résultante applique la classe de gouttes * /téléspectateur == Réunion :: VIEWER_ORGANIZER || $ meetingSettings-> participant_reopen)) ?>
- = Html::a(Yii::t('frontend', 'Make changes'), ['reopen','id'=>$ model-> id], ['title' => Yii :: t ('frontend', 'tbd')]); ?>
J'ai également créé des fichiers de vue partielle à rendre en fonction du statut d'une réunion. Par exemple, dans /frontend/views/meeting/view_confirmed.php, vous pouvez voir le
_command_bar_past.php
ou_command_bar_confirmed.php
partiel est inclus:status> = $ model :: STATUS_COMPLETED) … echo $ this-> render ('_ command_bar_past', ['model' => $ model, 'isPast' => true, 'dropclass' => 'dropdown', 'isOwner' => $ isOwner,]); else echo $ this-> render ('_ command_bar_confirmed', ['model' => $ model, 'meetingSettings' => $ meetingSettings, 'showRunningLate' => $ showRunningLate, 'isPast' => $ isPast, 'dropclass' => 'dropdown', 'isOwner' => $ isOwner,]); ?>Détermination de l'accès aux commandes pour différents visualiseurs
Je ne voulais pas permettre à tout le monde de voir toutes les commandes. Les organisateurs voient plus de commandes que de participants, mais les paramètres de la réunion leur accordent souvent un accès également..
Voici un exemple pour déterminer s’il faut afficher l’option permettant de rouvrir une réunion et d’y apporter des modifications comme si elle était encore en phase de planification:
téléspectateur == Réunion :: VIEWER_ORGANIZER || $ meetingSettings-> participant_reopen)) ?>
- = Html::a(Yii::t('frontend', 'Make changes'), ['reopen','id'=>$ model-> id], ['title' => Yii :: t ('frontend', 'tbd')]); ?>
L'option est incluse dans le menu déroulant Bootstrap si la réunion n'a pas dépassé sa date de début et que le spectateur est l'organisateur ou que l'organisateur a autorisé les participants à apporter des modifications..
Passons maintenant à la création de certaines fonctionnalités de ces différentes commandes..
Construire les commandes de planification avancées
Il n'est pas possible de couvrir tout le nouveau travail lié à ces fonctionnalités de planification. Par souci de brièveté, je ne couvrirai que les bases et tous les aspects uniques des différentes commandes.
Chaque fonctionnalité prend plus de temps maintenant
Après avoir implémenté des fonctionnalités de sécurité plus approfondies dans Meeting Planner, j'essaie de respecter des normes de codage plus strictes tout en ajoutant de nouvelles fonctionnalités. Je passe plus de temps à accéder aux contrôleurs, aux vérifications d'accès aux modèles et à la limitation de débit.
En outre, la version bêta prendra en charge plusieurs participants à une réunion. Je dois donc concevoir le code en conséquence..
Globalement, tout ce que je fais prend un peu plus longtemps qu'en mode de prototypage rapide.
Au fur et à mesure que je construisais toutes ces diverses fonctionnalités de réunion, je ressentais fréquemment l'augmentation de la charge de travail liée à l'ajout de la base de code. Vous remarquerez ces vérifications dans le code ci-dessous.
Une attention est également portée au journal de réunion historique que nous sommes sur le point de rendre visible. Ainsi, chaque action nécessite souvent un enregistrement de journal. Et les enregistrements de journal sont utiles pour supporter la limitation de débit.
Apporter des modifications à une réunion
Un moyen simple d’autoriser les modifications apportées à une réunion finalisée consiste tout simplement à permettre à l’organisateur de la rouvrir, ce qui lui permet de revenir à la phase de planification. Ils peuvent ensuite ajouter des dates et des lieux, en choisir de nouveaux et finaliser la réunion à nouveau..
Je voulais aussi que l'organisateur habilite ses participants à le faire. Cela a finalement nécessité de pouvoir visualiser et mettre à jour les paramètres par réunion..
J'ai ajouté des paramètres pour permettre aux participants de demander des modifications aux organisateurs ou de les modifier directement..
Lorsqu'une réunion est créée, ses paramètres sont initialisés avec les paramètres par défaut de l'utilisateur:
fonction publique initializeMeetingSetting ($ meeting_id, $ owner_id) $ checkMtgStg = MeetingSetting :: find () -> où (['meeting_id' => $ meeting_id]) -> un (); if (is_null ($ checkMtgStg)) // charge les paramètres utilisateur du créateur de la réunion (propriétaire) pour initialiser les paramètres de la réunion UserSetting :: initialize ($ propriétaire_id); // si non initialisé $ user_setting = UserSetting :: find () -> où (['user_id' => $ owner_id]) -> one (); $ meeting_setting = new MeetingSetting (); $ meeting_setting-> meeting_id = $ meeting_id; $ meeting_setting-> participant_add_place = $ user_setting-> participant_add_place; $ meeting_setting-> participant_add_date_time = $ user_setting-> participant_add_date_time; $ meeting_setting-> participant_choose_place = $ user_setting-> participant_choose_place; $ meeting_setting-> participant_choose_date_time = $ user_setting-> participant_choose_date_time; $ meeting_setting-> participant_finalize = $ user_setting-> participant_finalize; $ meeting_setting-> participant_reopen = $ user_setting-> participant_reopen; $ meeting_setting-> participant_request_change = $ user_setting-> participant_request_change; $ meeting_setting-> save ();Vous pouvez voir les résultats du nouveau contrôleur et de la vue de mise à jour pour MeetingSettings ci-dessous:
Voici /frontend/controllers/MeetingController.php's
actionReopen ()
:fonction publique actionReopen ($ id) $ m = $ this-> findModel ($ id); $ m-> setViewer (); // vérifie également reopen () if ($ m-> spectateur == Réunion :: VIEWER_ORGANIZER || $ m-> meetingSettings-> participant_reopen) if ($ m-> reopen ()) Yii :: $ app-> getSession () -> setFlash ('success', Yii :: t ('frontend', 'La réunion a maintenant été rouverte afin que vous puissiez apporter des modifications.')); else Yii :: $ app-> getSession () -> setFlash ('erreur', Yii :: t ('frontend', 'désolé, vous n'êtes pas autorisé à rouvrir une réunion autant de fois. Essayez de créer une nouvelle réunion. . ')); else Yii :: $ app-> getSession () -> setFlash ('error', Yii :: t ('frontend', 'Désolé, vous n'êtes pas autorisé à le faire.')); return $ this-> redirect (['view', 'id' => $ id]);Et voici le code de modèle Meeting.php pour déplacer la réunion en mode de planification:
fonction publique reopen () // lorsque l'organisateur ou le participant disposant d'une autorisation demande à apporter des modifications si (MeetingLog :: insideActionLimit ($ this-> id, MeetingLog :: ACTION_REOPEN, Yii :: $ app-> user-> getId (), 7)) $ this-> status = Meeting :: STATUS_SENT; $ this-> update (); $ this-> augmentationSequence (); MeetingLog :: add ($ this-> id, MeetingLog :: ACTION_REOPEN, Yii :: $ app-> user-> getId ()); retourne vrai; else // limite supérieure par réunion return false;le
dans ActionLimit
vérifie le nombre de fois où quelqu'un tente de rouvrir une réunion.AugmentationSéquence
est pour le fichier .ics: la date, l'heure et le lieu de la réunion doivent être modifiés, le fichier ics doit être indiqué.L'image ci-dessous montre une réunion confirmée avec diverses options avancées disponibles:
Lorsque l'utilisateur clique Faire des changements dans le menu ci-dessus, le statut de la réunion est remis à la planification et ils peuvent revenir pour mettre à jour la date, l'heure et le lieu:
Replanifier une réunion
Si les événements ont amené les participants à se rendre compte qu'ils ont juste besoin de recommencer, le Replanifier option annule la réunion en cours et crée une nouvelle invitation à la planification.
Actuellement, je limite cette fonctionnalité aux organisateurs (pas aux participants), mais je peux l'étendre plus tard. le
Meeting.php :: Replanifier ()
La méthode prend en charge l'une ou l'autre personne effectuant l'action:fonction publique reschedule () $ newOwner = $ user_id = Yii :: $ app-> user-> getId (); // l'utilisateur ne peut annuler sa propre réunion que si ($ this-> owner_id == $ user_id) $ addParticipant = false; $ this-> cancel ($ user_id); MeetingLog :: add ($ this-> id, MeetingLog :: ACTION_RESCHEDULE, $ user_id); else // si l'utilisateur est participant - doit inverser si (! isAttendee ($ this-> id, $ user_id)) // l'utilisateur n'est pas propriétaire ni participant - error return false; else // inverse le propriétaire et le participant $ addParticipant = $ this-> owner_id; // créer une nouvelle réunion - en tant que copie de l'ancienne réunion $ m = new Meeting (); $ m-> attributs = $ this-> attributs; $ m-> owner_id = $ newOwner; $ m-> status = Meeting :: STATUS_PLANNING; $ m-> created_at = time (); $ m-> updated_at = time (); $ m-> logs_at = 0; $ m-> cleared_at = 0; $ m-> sequence_id = 0; $ m-> save (); // clone le lieu sélectionné (pas tous) $ selectedPlace = $ this-> getChosenPlace ($ this-> id); if ($ selectedPlace! == false) $ mp = new MeetingPlace; $ mp-> suggérée_by = $ newOwner; $ mp-> attributs = $ selectedPlace-> attributs; $ mp-> meeting_id = $ m-> id; $ mp-> created_at = time (); $ mp-> updated_at = time (); $ mp-> save (); // clone les participants pour chaque ($ this-> participants sous la forme $ p) // ignorer si le nouveau propriétaire est replanifié comme participant si ($ p-> participant_id == $ utilisateur_id) continue; // note Le participant afterSave créera des choix pour le lieu $ clone_p = new Participant (); $ clone_p-> attributs = $ p-> attributs; $ clone_p-> email = Utilisateur :: findOne ($ p-> participant_id) -> email; $ clone_p-> meeting_id = $ m-> id; $ clone_p-> guest_by = $ newOwner; $ clone_p-> status = Participant :: STATUS_DEFAULT; $ clone_p-> created_at = time (); $ clone_p-> updated_at = time (); $ clone_p-> save (); // si le participant a été invité à replanifier - pas encore autorisé if ($ addParticipant! == false) $ newP = new Participant (); $ newP-> meeting_id = $ m-> id; $ newP-> participant_id = $ addParticipant; $ newP-> guest_by = $ user_id; $ newP-> status = Participant :: STATUS_DEFAULT; $ newP-> created_at = time (); $ newP-> updated_at = time (); $ newP-> save (); return $ m-> id;Les participants et le lieu sélectionné sont clonés dans la nouvelle réunion..
Répéter une réunion
Une autre approche de la planification consiste à permettre aux participants de dupliquer des réunions passées. À l'avenir, je pourrais laisser le spectateur décider quels participants, lieux et heures sont dupliqués, mais pour l'instant, Meeting Planner crée une nouvelle réunion avec les mêmes participants au même endroit, ainsi qu'un jour de la semaine et une heure identiques. deux semaines dans le futur.
public function repeat () // à faire - développez la répétition d'une réunion pour avoir plus d'options // par ex. choisir le même jour et la même heure dans les deux semaines à venir // p. ex. dupliquer le lieu choisi ou tous les lieux // par ex. dupliquer tous les participants ou juste quelques uns (compliqué si le participant duplique) $ newOwner = $ user_id = Yii :: $ app-> user-> getId (); // si l'utilisateur est participant - doit inverser si ($ this-> propriétaire_id == $ utilisateur_id) $ addParticipant = false; else if (! isAttendee ($ this-> id, $ id_utilisateur)) // l'utilisateur n'est ni propriétaire ni participant - error return false; else // inverse le propriétaire et le participant $ addParticipant = $ this-> owner_id; // créer une nouvelle réunion - en tant que copie de l'ancienne réunion $ m = new Meeting (); $ m-> attributs = $ this-> attributs; $ m-> owner_id = $ newOwner; $ m-> status = Meeting :: STATUS_PLANNING; $ m-> created_at = time (); $ m-> updated_at = time (); $ m-> logs_at = 0; $ m-> cleared_at = 0; $ m-> sequence_id = 0; $ m-> save (); // récupère l'heure des réunions précédentes et crée deux heures futures pour les deux prochaines semaines $ selectedTime = $ this-> getChosenTime ($ this-> id); $ mt1 = MeetingTime :: createTimePlus ($ m-> id, $ m-> propriétaire_id, $ ChoisiTime-> Début, $ ChoisiTime-> Durée); $ mt2 = MeetingTime :: createTimePlus ($ m-> id, $ m-> propriétaire_id, $ mt1-> début, $ ChoisiTime-> durée); // clone le lieu sélectionné (pas tous) $ selectedPlace = $ this-> getChosenPlace ($ this-> id); if ($ selectedPlace! == false) $ mp = new MeetingPlace; $ mp-> suggérée_by = $ newOwner; $ mp-> attributs = $ selectedPlace-> attributs; $ mp-> meeting_id = $ m-> id; $ mp-> created_at = time (); $ mp-> updated_at = time (); $ mp-> save (); // clone les participants pour chaque ($ this-> participants sous la forme $ p) // ignorer si le nouveau propriétaire est replanifié comme participant si ($ p-> participant_id == $ utilisateur_id) continue; // note Le participant afterSave créera des choix pour le lieu $ clone_p = new Participant (); $ clone_p-> attributs = $ p-> attributs; $ clone_p-> email = Utilisateur :: findOne ($ p-> participant_id) -> email; $ clone_p-> meeting_id = $ m-> id; $ clone_p-> status = Participant :: STATUS_DEFAULT; $ clone_p-> created_at = time (); $ clone_p-> updated_at = time (); $ clone_p-> save (); // si le participant est invité à répéter // ajouter l'ancien propriétaire en tant que participant if ($ addParticipant! == false) $ newP = new Participant (); $ newP-> meeting_id = $ m-> id; $ newP-> participant_id = $ addParticipant; $ newP-> guest_by = $ user_id; $ newP-> status = Participant :: STATUS_DEFAULT; $ newP-> created_at = time (); $ newP-> updated_at = time (); $ newP-> save (); MeetingLog :: add ($ this-> id, MeetingLog :: ACTION_REPEAT, $ user_id, 0); retourne $ m-> id;
MeetingTime :: createTimePlus ()
ci-dessous ajoute une heure de réunion le même jour de la semaine et l'heure, mais une semaine dans le futur, même si la réunion d'origine a eu lieu il y a plusieurs mois. letandis que
la boucle était nécessaire pour les réunions plus anciennes.Fonction statique publique createTimePlus ($ meeting_id, $ suggest_by, $ start, $ duration, $ timeInFuture = 604800) // recherche le temps en multiples d'une semaine ou timeInFuture quelques secondes après l'heure actuelle $ newStart = $ start + $ timeInFuture; while ($ newStartRenvoyer les invitations
J'ai également créé une fonction de renvoi au cas où un participant ne recevrait pas l'invitation d'origine ou la confirmation finale.
fonction statique publique resend ($ id) $ sender_id = Yii :: $ app-> user-> getId (); // vérifie si dans les limites de renvoi $ cnt = MeetingLog :: find () -> où (['actor_id' => $ sender_id]) -> andWhere (['meeting_id' => $ id]) -> andWhere ([' action '=> MeetingLog :: ACTION_RESEND]) -> count (); if ($ cnt> = Meeting :: RESEND_LIMIT) return false; else $ m = Meeting :: findOne ($ id); if ($ m-> statut == réunion :: STATUS_SENT) $ m-> send ($ sender_id, true); // renvoie l'invitation à planifier else if ($ m-> statut == Réunion :: STATUS_CONFIRMED) // renvoie l'invitation confirmée $ m-> finalize ($ sender_id, true); MeetingLog :: add ($ id, MeetingLog :: ACTION_RESEND, $ sender_id, 0); retourne vrai;Je prévois de reconstruire la fonctionnalité de messagerie sortante pour qu'elle fonctionne de manière asynchrone et facilite la redistribution, mais heureusement, les méthodes actuelles ont bien fonctionné dans le scénario de renvoi, avec peu de modifications..
Historique de la réunion
Tout au long de la série, nous avons créé un journal de chaque changement apporté aux réunions. Les participants peuvent désormais consulter l'historique de planification. Cela ressemble à ceci:
MeetingLogController.php vérifie que le visualiseur est un participant de la réunion et prépare les données pour afficher le journal:
fonction publique actionView ($ id) if (! Meeting :: isAttendee ($ id, Yii :: $ app-> user-> getId ())) $ this-> redirect (['site / authfailure']); $ timezone = MiscHelpers :: fetchUserTimezone (Yii :: $ app-> user-> getId ()); Yii :: $ app-> timeZone = $ timezone; $ searchModel = new MeetingLogSearch (); $ dataProvider = $ searchModel-> search (['MeetingLogSearch' => ['meeting_id' => $ id]]); $ m = Meeting :: findOne ($ id); return $ this-> render ('index', ['searchModel' => $ searchModel, 'dataProvider' => $ dataProvider, 'meeting_id' => $ id, 'subject' => $ m-> getMeetingHeader ('log' ), 'timezone' => $ timezone,]);Ensuite, le fichier /frontend/views/meeting-log/index.php rend les données ci-dessus:
titre)?>
$ dataProvider, // 'filterModel' => $ searchModel, 'colonnes' => [['label' => 'Acteur', 'attribut' => 'actor_id', 'format' => 'raw', 'valeur' => fonction ($ modèle) return ''.MiscHelpers :: getDisplayName ($ model-> actor_id).''; ,], ['label' => 'Action', 'attribut' => 'action', 'format' => 'raw', 'value' => function ($ model) return ''. $ model-> getMeetingLogCommand ().''; ,], ['label' => 'Item', 'attribut' => 'Item_id', 'format' => 'raw', 'value' => function ($ model) return ''. $ model-> getMeetingLogItem ().''; ,], ['label' => 'Créé', 'attribut' => 'created_at', 'format' => 'raw', 'value' => function ($ model) return ''.Yii :: $ app-> formateur-> asDatetime ($ model-> created_at, "hh: ss MMM d").''; ,],],]); ?> = Html::a(Yii::t('frontend', 'Return to Meeting'), ['meeting/view', 'id' => $ meeting_id], ['class' => 'btn btn-primaire btn-info', 'title' => Yii :: t ('frontend', 'Retour à la page de la réunion'),]); ?>Et après?
Je suis maintenant sur un sprint de code intense pour compléter la version bêta. Les dieux de la rédaction chez Envato Tuts + ont fait de leur mieux pour me distraire avec des robots à contrôle mental et des bips de type OKCupid émis par leur application de flux de travail utilisant iOS, mais ils ont échoué. Le développement de Meeting Planner se poursuit à un rythme soutenu.
La résolution de l'ingénierie sociale et de l'expérience utilisateur pour permettre aux participants à la réunion de demander et de réagir à des ajustements mineurs de la planification pourrait faire ou défaire la marque Meeting Planner, et c'est ce sur quoi je travaille le plus..
Si vous ne l'avez pas encore fait, organisez votre première réunion avec Meeting Planner. Je prévois également d'écrire un didacticiel sur le financement participatif. Veuillez donc suivre notre page de planification de réunions WeFunder. Vous pouvez également me contacter @reifman. Je suis toujours ouvert aux nouvelles idées de fonctionnalités et suggestions de sujets pour les prochains tutoriels.
Restez à l'affût de tout cela et de plusieurs autres tutoriels à venir en consultant la série Construire son démarrage avec PHP.
Liens connexes
- Planificateur de réunion
- Suivre le profil de financement de Meeting Planner
- Programmation avec la série Yii2 (Envato Tuts +)