├── LICENSE
├── composer.json
├── config
└── ambulatory.php
├── package.json
├── public
├── app.css
├── app.js
└── mix-manifest.json
├── readme.md
├── resources
├── js
│ ├── app.js
│ ├── base.js
│ ├── bus.js
│ ├── components
│ │ ├── Alert.vue
│ │ ├── AvatarUpload.vue
│ │ ├── Booking.vue
│ │ ├── FormEntry.vue
│ │ ├── FormErrors.vue
│ │ ├── IndexView.vue
│ │ ├── Modal.vue
│ │ ├── SelectGender.vue
│ │ ├── SelectHealthFacility.vue
│ │ ├── SelectMaritalStatus.vue
│ │ ├── SettingTabs.vue
│ │ └── SidebarMenu.vue
│ ├── routes.js
│ └── views
│ │ ├── accounts
│ │ └── edit.vue
│ │ ├── doctors
│ │ ├── edit.vue
│ │ ├── index.vue
│ │ └── preview.vue
│ │ ├── errors
│ │ └── 404.vue
│ │ ├── facilities
│ │ ├── edit.vue
│ │ └── index.vue
│ │ ├── inbox
│ │ ├── index.vue
│ │ └── preview.vue
│ │ ├── invitations
│ │ ├── edit.vue
│ │ └── index.vue
│ │ ├── medical
│ │ ├── edit.vue
│ │ └── index.vue
│ │ ├── password
│ │ └── new.vue
│ │ ├── schedules
│ │ ├── edit.vue
│ │ └── index.vue
│ │ ├── specializations
│ │ ├── edit.vue
│ │ └── index.vue
│ │ └── staff
│ │ └── index.vue
├── sass
│ ├── app.scss
│ ├── base.scss
│ ├── card.scss
│ ├── modal.scss
│ ├── multiselect.scss
│ └── sidebar.scss
└── views
│ ├── auth
│ ├── login.blade.php
│ ├── register.blade.php
│ ├── request-password-reset.blade.php
│ └── reset-password.blade.php
│ ├── emails
│ ├── credential.blade.php
│ ├── forgot-password.blade.php
│ └── invitation.blade.php
│ └── layouts
│ ├── auth.blade.php
│ └── dashboard.blade.php
├── routes
├── auth.php
└── dashboard.php
├── src
├── Ambulatory.php
├── AmbulatoryModel.php
├── AmbulatoryServiceProvider.php
├── Availability.php
├── Booking.php
├── Console
│ ├── InstallCommand.php
│ └── MigrateCommand.php
├── Doctor.php
├── HasSlug.php
├── HasUuid.php
├── HealthFacility.php
├── Http
│ ├── Controllers
│ │ ├── AmbulatoryController.php
│ │ ├── Auth
│ │ │ ├── AcceptInvitationController.php
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ └── RegisterController.php
│ │ ├── AvailabilityController.php
│ │ ├── BookAppointmentController.php
│ │ ├── Controller.php
│ │ ├── DoctorController.php
│ │ ├── HealthFacilityController.php
│ │ ├── InboxController.php
│ │ ├── InvitationController.php
│ │ ├── MedicalFormController.php
│ │ ├── ScheduleAvailabilityController.php
│ │ ├── ScheduleController.php
│ │ ├── Settings
│ │ │ ├── AccountController.php
│ │ │ ├── DoctorProfileController.php
│ │ │ ├── NewPasswordController.php
│ │ │ └── UploadUserAvatarController.php
│ │ ├── SpecializationController.php
│ │ └── StaffController.php
│ ├── Middleware
│ │ ├── Admin.php
│ │ ├── Authenticate.php
│ │ ├── Doctor.php
│ │ ├── RedirectIfAuthenticated.php
│ │ └── VerifiedDoctor.php
│ ├── Requests
│ │ ├── AccountRequest.php
│ │ ├── AvailabilityRequest.php
│ │ ├── BookAppointmentRequest.php
│ │ ├── DoctorProfileRequest.php
│ │ ├── HealthFacilityRequest.php
│ │ ├── InvitationRequest.php
│ │ ├── MedicalFormRequest.php
│ │ ├── NewPasswordRequest.php
│ │ ├── ScheduleAvailabilityRequest.php
│ │ ├── ScheduleRequest.php
│ │ └── SpecializationRequest.php
│ └── Resources
│ │ ├── AvailabilityResource.php
│ │ ├── BookingResource.php
│ │ ├── DoctorResource.php
│ │ ├── HealthFacilityResource.php
│ │ ├── InvitationResource.php
│ │ ├── MedicalFormResource.php
│ │ ├── ScheduleResource.php
│ │ ├── SpecializationResource.php
│ │ └── UserResource.php
├── Invitation.php
├── Mail
│ ├── CredentialEmail.php
│ ├── InvitationEmail.php
│ └── ResetPasswordEmail.php
├── MedicalForm.php
├── Migrations
│ └── 2019_02_13_000000_create_tables.php
├── Policies
│ ├── AvailabilityPolicy.php
│ ├── DoctorPolicy.php
│ ├── DoctorSchedulePolicy.php
│ └── MedicalFormPolicy.php
├── Rules
│ ├── BookingAvailabilityRule.php
│ └── CurrentPassRule.php
├── Schedule.php
├── Specialization.php
└── User.php
├── webpack.mix.js
└── yarn.lock
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) David H. Sianturi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ambulatory/ambulatory",
3 | "description": "An open source ambulatory care platform",
4 | "keywords": ["laravel", "outpatient care", "ambulatory care", "scheduling appointment"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "David H. Sianturi",
9 | "email": "davidhsianturi@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^7.1.3",
14 | "laravel/framework": "~5.7.0|~5.8.0",
15 | "rlanvin/php-rrule": "^2.0"
16 | },
17 | "require-dev": {
18 | "orchestra/testbench": "^3.7"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Ambulatory\\": "src/"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "Ambulatory\\Tests\\": "tests/"
28 | }
29 | },
30 | "extra": {
31 | "branch-alias": {
32 | "dev-master": "1.0-dev"
33 | },
34 | "laravel": {
35 | "providers": [
36 | "Ambulatory\\AmbulatoryServiceProvider"
37 | ]
38 | }
39 | },
40 | "config": {
41 | "sort-packages": true
42 | },
43 | "minimum-stability": "dev",
44 | "prefer-stable": true
45 | }
46 |
--------------------------------------------------------------------------------
/config/ambulatory.php:
--------------------------------------------------------------------------------
1 | env('AMBULATORY_DB_CONNECTION', 'ambulatory'),
12 |
13 | /*
14 | |--------------------------------------------------------------------------
15 | | Ambulatory Uploads Disk
16 | |--------------------------------------------------------------------------
17 | |
18 | */
19 |
20 | 'storage_disk' => env('AMBULATORY_STORAGE_DISK', 'local'),
21 |
22 | 'storage_path' => env('AMBULATORY_STORAGE_PATH', 'public/ambulatory'),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Ambulatory Path
27 | |--------------------------------------------------------------------------
28 | |
29 | */
30 | 'path' => env('AMBULATORY_PATH', 'ambulatory'),
31 | ];
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.19",
14 | "bootstrap": "^4.3.1",
15 | "cross-env": "^5.2",
16 | "jquery": "^3.3.1",
17 | "laravel-mix": "^4.0.7",
18 | "lodash": "^4.17.15",
19 | "moment": "^2.24.0",
20 | "popper.js": "^1.14.7",
21 | "resolve-url-loader": "^2.3.1",
22 | "sass": "^1.15.2",
23 | "sass-loader": "^7.1.0",
24 | "vue": "^2.6.10",
25 | "vue-router": "^3.0.7",
26 | "vue-template-compiler": "^2.6.6"
27 | },
28 | "dependencies": {
29 | "vue-multiselect": "^2.1.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/public/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=d432af3336390d493bb8",
3 | "/app.css": "/app.css?id=7b3dcba4823855b1ffd2"
4 | }
5 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | Ambulatory is An experiment project for an outpatient basis, including diagnosis, observation, consultation, treatment, intervention, and rehabilitation services [Learn more about ambulatory care >>>](https://www.rasmussen.edu/degrees/nursing/blog/what-is-ambulatory-care/)
4 |
5 | ## Installation
6 |
7 | Ambulatory runs on any Laravel application, it uses a separate database connection and authentication system so that you don't have to modify any of your project code.
8 |
9 | Install Ambulatory via Composer:
10 |
11 | ```
12 | composer require ambulatory/ambulatory
13 | ```
14 |
15 | Once Composer is done, run the following command.
16 |
17 | ```
18 | php artisan ambulatory:install
19 | ```
20 |
21 | Create a symbolic link to ensure file uploads are publicly accessible from the web:
22 |
23 | ```
24 | php artisan storage:link
25 | ```
26 |
27 | Check `config/ambulatory.php` and configure the database connection ambulatory is going to be using, after that go run:
28 |
29 | ```
30 | php artisan ambulatory:migrate
31 | ```
32 |
33 | Head to `yourproject.test/ambulatory` and use the provided email and password to log in.
34 |
35 | ## Road map
36 |
37 | Ambulatory is still under heavy development, I decided to ship it in this early stage so you can help me make it better. [See the road map >>>](https://github.com/ambulatorycare/ambulatory/projects/1)
38 |
39 | ## Security Vulnerabilities
40 |
41 | If you discover a security vulnerability within Ambulatory, please send an e-mail to [davidhsianturi@gmail.com](mailto:davidhsianturi@gmail.com). All security vulnerabilities will be promptly addressed.
42 |
43 | ## License
44 |
45 | Ambulatory is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
46 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Base from './base';
3 | import {Bus} from './bus.js';
4 | import Routes from './routes';
5 | import VueRouter from 'vue-router';
6 |
7 | require('bootstrap');
8 |
9 | Vue.use(VueRouter);
10 |
11 | window.Popper = require('popper.js').default;
12 |
13 | const router = new VueRouter({
14 | routes: Routes,
15 | mode: 'history',
16 | base: '/' + Ambulatory.path,
17 | });
18 |
19 | Vue.component('alert', require('./components/Alert.vue').default);
20 | Vue.component('modal', require('./components/Modal.vue').default);
21 | Vue.component('form-entry', require('./components/FormEntry.vue').default);
22 | Vue.component('index-view', require('./components/IndexView.vue').default);
23 | Vue.component('form-errors', require('./components/FormErrors.vue').default);
24 | Vue.component('sidebar-menu', require('./components/SidebarMenu.vue').default);
25 | Vue.component('booking', require('./components/Booking.vue').default);
26 |
27 | Vue.mixin(Base);
28 |
29 | new Vue({
30 | el: '#ambulatory',
31 |
32 | router,
33 |
34 | data() {
35 | return {
36 | alert: {
37 | mode: null,
38 | type: null,
39 | autoClose: 0,
40 | message: '',
41 | confirmationProceed: null,
42 | confirmationCancel: null,
43 | },
44 |
45 | booking: {
46 | schedule: null,
47 | }
48 | }
49 | },
50 |
51 | mounted() {
52 | Bus.$on('httpError', message => this.alertError(message));
53 | Bus.$on('httpForbidden', message => this.alertError(message));
54 | }
55 | });
--------------------------------------------------------------------------------
/resources/js/base.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import axios from 'axios';
3 | import moment from 'moment';
4 | import {Bus} from './bus.js';
5 |
6 | export default {
7 | computed: {
8 | Ambulatory() {
9 | return Ambulatory;
10 | }
11 | },
12 |
13 | methods: {
14 | /**
15 | * Show the local date for today.
16 | */
17 | localToday() {
18 | return moment().local();
19 | },
20 |
21 | /**
22 | * Show the local date time.
23 | */
24 | localDateTime(dateTime) {
25 | return moment(dateTime)
26 | .utc()
27 | .local();
28 | },
29 |
30 | /**
31 | * Show the time ago format for the given time.
32 | */
33 | timeAgo(time) {
34 | return moment(time)
35 | .utc()
36 | .local()
37 | .fromNow();
38 | },
39 |
40 | /**
41 | * Show the duration format for the given date.
42 | */
43 | dateDuration(date) {
44 | return moment(date)
45 | .utc()
46 | .local()
47 | .fromNow(true);
48 | },
49 |
50 | /**
51 | * Show a success message.
52 | */
53 | alertSuccess(message, autoClose) {
54 | this.$root.alert.mode = 'flash';
55 | this.$root.alert.type = 'success';
56 | this.$root.alert.autoClose = autoClose;
57 | this.$root.alert.message = message;
58 | },
59 |
60 | /**
61 | * Show an error message.
62 | */
63 | alertError(message) {
64 | this.$root.alert.mode = 'dialog';
65 | this.$root.alert.type = 'error';
66 | this.$root.alert.autoClose = false;
67 | this.$root.alert.message = message;
68 | },
69 |
70 | /**
71 | * Show confirmation message.
72 | */
73 | alertConfirm(message, success, failure) {
74 | this.$root.alert.mode = 'dialog';
75 | this.$root.alert.type = 'confirmation';
76 | this.$root.alert.autoClose = false;
77 | this.$root.alert.message = message;
78 | this.$root.alert.confirmationProceed = success;
79 | this.$root.alert.confirmationCancel = failure;
80 | },
81 |
82 | /**
83 | * Show a booking dialog.
84 | */
85 | bookAppointment(schedule) {
86 | this.$root.booking.schedule = schedule;
87 | },
88 |
89 | /**
90 | * Truncate the given string.
91 | */
92 | truncate(string, length = 70) {
93 | return _.truncate(string, {
94 | 'length': length,
95 | 'separator': /,? +/
96 | });
97 | },
98 |
99 | /**
100 | * Create an instance of axios.
101 | */
102 | http() {
103 | let instance = axios.create();
104 |
105 | instance.defaults.baseURL = '/' + Ambulatory.path;
106 |
107 | instance.interceptors.response.use(
108 | response => response,
109 | error => {
110 | switch (error.response.status) {
111 | case 500:
112 | Bus.$emit('httpError', error.response.data.message);
113 | break;
114 | case 403:
115 | Bus.$emit('httpForbidden', error.response.data.message);
116 | break;
117 | case 401:
118 | window.location.href = '/' + Ambulatory.path + '/logout';
119 | break;
120 | }
121 |
122 | return Promise.reject(error);
123 | }
124 | );
125 |
126 | return instance;
127 | },
128 | }
129 | };
--------------------------------------------------------------------------------
/resources/js/bus.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | export const Bus = new Vue();
--------------------------------------------------------------------------------
/resources/js/components/Alert.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {{message}}
64 |
65 |
66 |
67 |
68 |
69 |
70 | OK
71 |
72 |
73 |
74 |
75 | NO
76 |
77 |
78 | YES
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | {{message}}
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/resources/js/components/AvatarUpload.vue:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/resources/js/components/Booking.vue:
--------------------------------------------------------------------------------
1 |
80 |
81 |
82 |
83 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/resources/js/components/FormEntry.vue:
--------------------------------------------------------------------------------
1 |
76 |
77 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Loading...
91 |
92 |
93 |
94 |
95 |
No data were found
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/resources/js/components/FormErrors.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | {{error}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/resources/js/components/IndexView.vue:
--------------------------------------------------------------------------------
1 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 | Loading...
66 |
67 |
68 |
69 |
No data were found
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
86 |
87 |
Loading...
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/resources/js/components/Modal.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/js/components/SelectGender.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
36 |
37 |
--------------------------------------------------------------------------------
/resources/js/components/SelectHealthFacility.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/resources/js/components/SelectMaritalStatus.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
36 |
37 |
--------------------------------------------------------------------------------
/resources/js/components/SettingTabs.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 |
35 |
40 |
41 | Account
42 |
43 |
44 |
49 |
50 | Password
51 |
52 |
53 |
59 |
60 | Doctor Profile
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/resources/js/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {path: '/', redirect: '/inbox'},
3 |
4 | // Inbox.
5 | {
6 | path: '/inbox',
7 | name: 'inbox',
8 | component: require('./views/inbox/index').default
9 | },
10 | {
11 | path: '/inbox/:id',
12 | name: 'inbox-preview',
13 | component: require('./views/inbox/preview').default
14 | },
15 |
16 | // Schedules.
17 | {
18 | path: '/schedules',
19 | name: 'schedules',
20 | component: require('./views/schedules/index').default
21 | },
22 | {
23 | path: '/schedules/new',
24 | name: 'schedules-new',
25 | component: require('./views/schedules/edit').default
26 | },
27 | {
28 | path: '/schedules/:id',
29 | name: 'schedules-edit',
30 | component: require('./views/schedules/edit').default
31 | },
32 |
33 | // Medical form.
34 | {
35 | path: '/medical-forms',
36 | name: 'medical-forms',
37 | component: require('./views/medical/index').default
38 | },
39 | {
40 | path: '/medical-forms/new',
41 | name: 'medical-forms-new',
42 | component: require('./views/medical/edit').default
43 | },
44 | {
45 | path: '/medical-forms/:id',
46 | name: 'medical-forms-edit',
47 | component: require('./views/medical/edit').default
48 | },
49 |
50 | // Staff.
51 | {
52 | path: '/staff',
53 | name: 'staff',
54 | component: require('./views/staff/index').default
55 | },
56 |
57 | // Invitations.
58 | {
59 | path: '/invitations',
60 | name: 'invitations',
61 | component: require('./views/invitations/index').default
62 | },
63 | {
64 | path: '/invitations/new',
65 | name: 'invitations-new',
66 | component: require('./views/invitations/edit').default
67 | },
68 | {
69 | path: '/invitations/:id',
70 | name: 'invitations-edit',
71 | component: require('./views/invitations/edit').default
72 | },
73 |
74 | // Specializations.
75 | {
76 | path: '/specializations',
77 | name: 'specializations',
78 | component: require('./views/specializations/index').default
79 | },
80 | {
81 | path: '/specializations/new',
82 | name: 'specializations-new',
83 | component: require('./views/specializations/edit').default
84 | },
85 | {
86 | path: '/specializations/:id',
87 | name: 'specializations-edit',
88 | component: require('./views/specializations/edit').default
89 | },
90 |
91 | // Health facilities.
92 | {
93 | path: '/health-facilities',
94 | name: 'health-facilities',
95 | component: require('./views/facilities/index').default
96 | },
97 | {
98 | path: '/health-facilities/new',
99 | name: 'health-facilities-new',
100 | component: require('./views/facilities/edit').default
101 | },
102 | {
103 | path: '/health-facilities/:id',
104 | name: 'health-facilities-edit',
105 | component: require('./views/facilities/edit').default
106 | },
107 |
108 | // Settings.
109 | {
110 | path: '/settings/account',
111 | name: 'settings-account',
112 | component: require('./views/accounts/edit').default
113 | },
114 | {
115 | path: '/settings/password/new',
116 | name: 'new-password',
117 | component: require('./views/password/new').default
118 | },
119 | {
120 | path: '/settings/doctor-profile',
121 | name: 'settings-doctor-profile',
122 | component: require('./views/doctors/edit').default
123 | },
124 |
125 | {
126 | path: '/doctors',
127 | name: 'doctors',
128 | component: require('./views/doctors/index').default
129 | },
130 | {
131 | path: '/doctors/:id',
132 | name: 'doctors-preview',
133 | component: require('./views/doctors/preview').default
134 | },
135 |
136 | // Catch All.
137 | {
138 | path: '*',
139 | name: 'catch-all',
140 | component: require('./views/errors/404').default,
141 | },
142 | ];
--------------------------------------------------------------------------------
/resources/js/views/accounts/edit.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Loading...
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
75 |
76 |
88 |
89 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/resources/js/views/doctors/edit.vue:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
57 |
58 |
70 |
71 |
83 |
84 |
113 |
114 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/resources/js/views/doctors/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
26 |
27 |
{{slotProps.entry.is_active ? 'Active' : 'Not active'}}
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/resources/js/views/doctors/preview.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
36 |
37 |
{{slotProps.formData.is_active ? 'Active' : 'Not active'}}
38 |
39 |
40 |
43 |
44 |
45 |
46 | {{slotProps.formData.full_name}}
47 | don't have any schedules yet.
48 |
49 |
50 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/resources/js/views/facilities/edit.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
21 |
22 |
34 |
35 |
47 |
48 |
60 |
61 |
73 |
74 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/resources/js/views/facilities/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | New
9 |
10 |
11 |
12 |
13 |
14 |
{{slotProps.entry.name}}
15 | {{timeAgo(slotProps.entry.created_at)}}
16 |
17 |
18 |
19 | {{slotProps.entry.address}},
20 | {{slotProps.entry.city}},
21 | {{slotProps.entry.zip_code}},
22 | {{slotProps.entry.state}}
23 | {{slotProps.entry.country}}
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/js/views/inbox/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Physical appointment
11 | {{timeAgo(slotProps.entry.created_at)}}
12 |
13 |
14 | Visit time: {{localDateTime(slotProps.entry.preferred_date_time).format('MMMM Do YYYY h:mm:ss A')}}
15 |
16 | Location:
17 | {{slotProps.entry.doctor_schedule.health_facility.name}},
18 | {{slotProps.entry.doctor_schedule.health_facility.address}},
19 | {{slotProps.entry.doctor_schedule.health_facility.city}},
20 | {{slotProps.entry.doctor_schedule.health_facility.state}},
21 | {{slotProps.entry.doctor_schedule.health_facility.country}}
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/js/views/inbox/preview.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 | Loading...
32 |
33 |
34 |
35 |
36 | 404 — Inbox not found
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Type
45 |
46 | Physical appointment
47 |
48 |
49 |
50 | Date time
51 |
52 | {{localDateTime(entry.preferred_date_time).format('MMMM Do YYYY h:mm:ss A')}}
53 |
54 |
55 |
56 | Location
57 |
58 | {{entry.doctor_schedule.health_facility.name}},
59 | {{entry.doctor_schedule.health_facility.address}},
60 | {{entry.doctor_schedule.health_facility.city}},
61 | {{entry.doctor_schedule.health_facility.state}},
62 | {{entry.doctor_schedule.health_facility.country}}
63 |
64 |
65 |
66 | Doctor name
67 |
68 | {{entry.doctor_schedule.doctor.full_name}}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Medical ID
86 |
87 | {{entry.medical_form.form_name}}
88 |
89 |
90 |
91 | Patient name
92 |
93 | {{entry.medical_form.full_name}}
94 |
95 |
96 |
97 | Age
98 |
99 | {{dateDuration(entry.medical_form.dob)}}
100 |
101 |
102 |
103 | Gender
104 |
105 | {{entry.medical_form.gender}}
106 |
107 |
108 |
109 | Marital status
110 |
111 | {{entry.medical_form.marital_status}}
112 |
113 |
114 |
115 | Address
116 |
117 | {{entry.medical_form.address}}, {{entry.medical_form.city}}
118 |
119 |
120 |
121 | Cell Phone
122 |
123 | {{entry.medical_form.cell_phone}}
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/resources/js/views/invitations/edit.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
35 |
36 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/resources/js/views/invitations/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | New
9 |
10 |
11 |
12 |
13 |
14 |
{{slotProps.entry.email}}
15 | {{timeAgo(slotProps.entry.created_at)}}
16 |
17 |
18 | {{slotProps.entry.role}}
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/js/views/medical/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | New
9 |
10 |
11 |
12 |
13 |
14 |
{{slotProps.entry.full_name}}
15 | {{slotProps.entry.form_name}}
16 |
17 |
18 | {{slotProps.entry.address}} - {{slotProps.entry.city}}
19 | Created {{timeAgo(slotProps.entry.created_at)}}
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/js/views/password/new.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
33 |
34 |
48 |
49 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/resources/js/views/schedules/edit.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
29 |
30 |
48 |
49 |
67 |
68 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/resources/js/views/schedules/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | New
9 |
10 |
11 |
12 |
13 |
14 |
{{slotProps.entry.health_facility.name}}
15 | {{timeAgo(slotProps.entry.created_at)}}
16 |
17 |
18 |
19 | {{localDateTime(slotProps.entry.start_date).format('MMMM Do YYYY')}}
20 | -
21 | {{localDateTime(slotProps.entry.end_date).format('MMMM Do YYYY')}}
22 |
23 |
24 | {{slotProps.entry.health_facility.address}},
25 | {{slotProps.entry.health_facility.city}},
26 | {{slotProps.entry.health_facility.zip_code}}
27 | {{slotProps.entry.health_facility.state}}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resources/js/views/specializations/edit.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
21 |
22 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/resources/js/views/specializations/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | New
9 |
10 |
11 |
12 |
13 |
14 |
{{slotProps.entry.name}}
15 | {{timeAgo(slotProps.entry.created_at)}}
16 |
17 |
18 | {{slotProps.entry.description}}
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/js/views/staff/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | Invite staff
9 |
10 |
11 |
12 |
13 |
14 |
26 |
27 |
{{slotProps.entry.role}}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 | $body-bg: #FFF;
2 | $font-size-base: 1rem;
3 | $font-weight-semibold: 600;
4 | $badge-font-size: 0.7rem;
5 | $badge-font-weight: normal;
6 |
7 | $primary: #40A6C8;
8 | $primary-light: #40A6C8A6;
9 | $secondary: #DAE1E7;
10 | $success: #51D88A;
11 | $info: #4090D1;
12 | $warning: #FFF382;
13 | $danger: #EF5753;
14 | $dark: #14171A;
15 | $gray: #687785;
16 | $light: #E7ECF0;
17 |
18 | $sidebar-nav-color: $dark;
19 | $sidebar-icon-color: $dark;
20 |
21 | $table-headers-color: #F1F5F8;
22 | $table-border-color: #F8FAFC;
23 | $table-hover-bg: #F1F7FA;
24 |
25 | $card-cap-bg: #FFF;
26 | $card-bg-light: #F1F7FA;
27 | $card-shadow-color: #CDD8DF;
28 | $card-border-radius: 0rem;
29 |
30 | $border-color: $light;
31 |
32 | $control-action-icon-color: #CCD2DF;
33 |
34 | $pill-link-active: $primary;
35 |
36 | $paginator-button-color: #9EA7AC;
37 |
38 | @import "base";
--------------------------------------------------------------------------------
/resources/sass/base.scss:
--------------------------------------------------------------------------------
1 | @import "multiselect";
2 | @import "~bootstrap/scss/bootstrap";
3 |
4 | // custom components.
5 | @import "card";
6 | @import "modal";
7 | @import "sidebar";
8 |
9 | [v-cloak] {
10 | display: none;
11 | }
12 |
13 | svg.icon {
14 | width: 1rem;
15 | height: 1rem;
16 | }
17 |
18 | // fill color
19 | .fill-text-color {
20 | fill: $body-color;
21 | }
22 |
23 | .fill-danger {
24 | fill: $danger;
25 | }
26 |
27 | .fill-warning {
28 | fill: $warning;
29 | }
30 |
31 | .fill-info {
32 | fill: $info;
33 | }
34 |
35 | .fill-success {
36 | fill: $success;
37 | }
38 |
39 | .fill-primary {
40 | fill: $primary;
41 | }
42 |
43 | // form auth
44 | .auth {
45 | display: -ms-flexbox;
46 | display: flex;
47 | -ms-flex-align: center;
48 | align-items: center;
49 | padding-top: 40px;
50 | padding-bottom: 40px;
51 | height: 100%;
52 | }
53 |
54 | .form-auth {
55 | width: 100%;
56 | max-width: 420px;
57 | padding: 15px;
58 | margin: auto;
59 | }
60 |
61 | .form-card {
62 | border-radius: 14px !important;
63 | border: 1px solid $border-color;
64 | padding: 40px 32px 12px;
65 | background-color: #fff;
66 | }
67 |
68 | // list group
69 | .list-group {
70 | a {
71 | svg {
72 | width: 1.2rem;
73 | height: 1.2rem;
74 | margin-right: 9px;
75 | fill: $dark;
76 | }
77 | }
78 | }
79 |
80 | .list-enter-active {
81 | transition: background 2s linear;
82 | }
83 |
84 | .list-enter,
85 | .list-leave-to {
86 | background: $secondary;
87 | }
88 |
89 | // button behavior
90 | button:hover {
91 | .fill-primary {
92 | fill: #FFF;
93 | }
94 | }
95 |
96 | .btn-outline-primary.active {
97 | .fill-primary {
98 | fill: $body-bg;
99 | }
100 | }
101 |
102 | .btn-outline-primary:not(:disabled):not(.disabled).active:focus {
103 | box-shadow: none !important;
104 | }
105 |
106 | // custom utilities
107 | .paginator {
108 | .btn {
109 | text-decoration: none;
110 | color: $paginator-button-color;
111 |
112 | &:hover {
113 | color: $primary;
114 | }
115 | }
116 | }
117 |
118 | .font-weight-bold {
119 | font-weight: $font-weight-semibold !important;
120 | }
121 |
122 | .rounded-full {
123 | border-radius: 9999px;
124 | }
125 |
126 | .rounded-xl {
127 | border-radius: 14px;
128 | }
129 |
130 | .shadow-xs {
131 | box-shadow: rgba(0, 0, 0, 0.08) 0px 8px 28px;
132 | }
133 |
134 | .form-info {
135 | padding: 2rem 2rem;
136 |
137 | svg {
138 | width: 1.7rem;
139 | height: 1.7rem;
140 | }
141 | }
--------------------------------------------------------------------------------
/resources/sass/card.scss:
--------------------------------------------------------------------------------
1 | .card {
2 | min-height: 100vh;
3 | border-color: $border-color;
4 |
5 | .bottom-radius {
6 | border-bottom-left-radius: $card-border-radius;
7 | border-bottom-right-radius: $card-border-radius;
8 | }
9 |
10 | .card-header {
11 | border-color: $border-color;
12 | background-color: $card-cap-bg;
13 |
14 | .btn {
15 | padding: 0.2rem 0.5rem;
16 | }
17 |
18 | h1 {
19 | margin: 0;
20 | color: $dark;
21 | font-size: 19px;
22 | font-weight: 800;
23 | }
24 | }
25 |
26 | .card-body {
27 | padding: 2rem 2rem;
28 | }
29 |
30 | .card-footer {
31 | padding: 1rem 2rem;
32 | border-color: $border-color;
33 | }
34 |
35 | .nav-pills {
36 | border-bottom: solid 1px $border-color;
37 | }
38 |
39 | .nav-pills .nav-link.active {
40 | background: none;
41 | color: $pill-link-active;
42 | border-bottom: solid 2px $primary;
43 | }
44 |
45 | .nav-pills .nav-link {
46 | border-radius: 0;
47 | font-size: 0.9rem;
48 | font-weight: bold;
49 | color: $gray;
50 | padding: 0.75rem 1.25rem;
51 | }
52 |
53 | .table {
54 | th,
55 | td {
56 | padding: 0.75rem 1.25rem;
57 | }
58 |
59 | &.table-sm {
60 | th,
61 | td {
62 | padding: 0.9rem 1.2rem;
63 | }
64 | }
65 |
66 | th {
67 | border-bottom: 0;
68 | font-weight: 400;
69 | padding: .5rem 1.25rem;
70 | background-color: $table-headers-color;
71 | }
72 |
73 | &:not(.table-borderless) {
74 | td {
75 | border-top: 1px solid $table-border-color;
76 | }
77 | }
78 |
79 | th.table-fit,
80 | td.table-fit {
81 | width: 1%;
82 | white-space: nowrap;
83 | }
84 | }
85 | }
86 |
87 | .card-bg-light {
88 | background: $card-bg-light;
89 | }
--------------------------------------------------------------------------------
/resources/sass/modal.scss:
--------------------------------------------------------------------------------
1 | #modal-full {
2 | position: fixed;
3 | z-index: 9998;
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100%;
8 | background: #FFF;
9 | transition: opacity 0.3s ease;
10 | }
11 |
12 | #modal-full .modal-container {
13 | background: #FFF;
14 | width: 100%;
15 | overflow: scroll;
16 | transition: all 0.3s ease;
17 | border-top: 0.6rem solid $primary;
18 | }
19 |
20 | #modal-dialog {
21 | position: absolute;
22 | z-index: 9998;
23 | width: 100%;
24 | height: 100%;
25 | background: rgba(0, 0, 0, .5);
26 | transition: opacity 0.3s ease;
27 | }
28 |
29 | #modal-dialog .modal-container {
30 | max-width: 350px;
31 | max-height: 100%;
32 | margin: 30px auto;
33 | background: #FFF;
34 | border-radius: 14px;
35 | padding-left: 20px;
36 | padding-right: 20px;
37 | padding-top: 30px;
38 | padding-bottom: 30px;
39 | transition: all 0.3s ease;
40 | }
41 |
42 | #modal-flash {
43 | position: fixed;
44 | z-index: 9998;
45 | bottom: 20px;
46 | right: 10px;
47 | }
48 |
49 | .modal-enter {
50 | opacity: 0;
51 | }
52 |
53 | .modal-leave-active {
54 | opacity: 0;
55 | }
56 |
57 | .modal-enter .modal-container,
58 | .modal-leave-active .modal-container {
59 | -webkit-transform: scale(1.1);
60 | transform: scale(1.1);
61 | }
--------------------------------------------------------------------------------
/resources/sass/multiselect.scss:
--------------------------------------------------------------------------------
1 | @import "~vue-multiselect/dist/vue-multiselect.min.css";
2 |
3 | .multiselect__tags {
4 | border: 0;
5 | background: $light;
6 | }
7 |
8 | .multiselect__tag {
9 | background: $primary-light;
10 | }
11 |
12 | .multiselect__input,
13 | .multiselect__single {
14 | background: $light;
15 | }
16 |
17 | .multiselect__option--highlight {
18 | background: $primary-light;
19 | }
20 |
21 | .multiselect__tag-icon:after {
22 | color: darken($primary-light, 15%);
23 | }
24 |
25 | .multiselect__tag-icon:focus,
26 | .multiselect__tag-icon:hover {
27 | background: $primary-light;
28 | }
29 |
30 | .multiselect__spinner:after,
31 | .multiselect__spinner:before {
32 | border-color: $primary;
33 | }
34 |
35 | .multiselect__content-wrapper {
36 | border: 0px;
37 | background: $light;
38 | }
39 |
40 | .multiselect--above .multiselect__content-wrapper {
41 | border-top: 0px;
42 | }
--------------------------------------------------------------------------------
/resources/sass/sidebar.scss:
--------------------------------------------------------------------------------
1 | .sidebar .nav-item {
2 | padding-top: 7px;
3 | padding-bottom: 7px;
4 |
5 | a {
6 | color: $sidebar-nav-color;
7 | padding: 10px;
8 | font-size: 19px;
9 | font-weight: bold;
10 | line-height: 1.31;
11 | overflow-wrap: break-word;
12 |
13 | svg {
14 | height: 1.75rem;
15 | width: 24px;
16 | color: $sidebar-icon-color;
17 | fill: none;
18 | stroke: currentColor;
19 | stroke-width: 1.7;
20 | margin-right: 20px;
21 | vertical-align: text-bottom;
22 | }
23 |
24 | &.active {
25 | color: $primary;
26 |
27 | svg {
28 | fill: none;
29 | stroke: $primary;
30 | }
31 | }
32 |
33 | }
34 |
35 | :hover {
36 | color: $primary;
37 |
38 | svg {
39 | fill: none;
40 | stroke: $primary;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/resources/views/auth/login.blade.php:
--------------------------------------------------------------------------------
1 | @extends('ambulatory::layouts.auth')
2 |
3 | @section('title', 'Sign In')
4 |
5 | @section('form-content')
6 |
7 |
Sign in
8 |
9 |
Forgot?
10 |
11 |
12 |
13 | @csrf
14 |
15 |
16 | E-mail address
17 |
18 |
24 |
25 | @if ($errors->has('email'))
26 |
27 | {{ $errors->first('email') }}
28 |
29 | @endif
30 |
31 |
32 |
33 | Password
34 |
35 |
40 |
41 | @if ($errors->has('password'))
42 |
43 | {{ $errors->first('password') }}
44 |
45 | @endif
46 |
47 |
48 |
59 |
60 |
61 | Sign in
62 |
63 |
64 |
65 | Don't have an account? Sign Up
66 |
67 |
68 | @endsection
--------------------------------------------------------------------------------
/resources/views/auth/register.blade.php:
--------------------------------------------------------------------------------
1 | @extends('ambulatory::layouts.auth')
2 |
3 | @section('title', 'Sign Up')
4 |
5 | @section('form-content')
6 |
7 |
Sign up
8 |
9 |
Sign in?
10 |
11 |
12 |
13 | @csrf
14 |
15 |
16 | Name
17 |
18 |
25 |
26 | @if ($errors->has('name'))
27 |
28 | {{ $errors->first('name') }}
29 |
30 | @endif
31 |
32 |
33 |
34 | E-mail address
35 |
36 |
41 |
42 | @if ($errors->has('email'))
43 |
44 | {{ $errors->first('email') }}
45 |
46 | @endif
47 |
48 |
49 |
50 | Password
51 |
52 |
58 |
59 | @if ($errors->has('password'))
60 |
61 | {{ $errors->first('password') }}
62 |
63 | @endif
64 |
65 |
66 |
67 | Confirm password
68 |
69 |
75 |
76 |
77 |
78 | Sign up
79 |
80 |
81 |
82 | Forgot your password?
83 | Reset!
84 |
85 |
86 | @endsection
--------------------------------------------------------------------------------
/resources/views/auth/request-password-reset.blade.php:
--------------------------------------------------------------------------------
1 | @extends('ambulatory::layouts.auth')
2 |
3 | @section('title', 'Reset password')
4 |
5 | @section('form-content')
6 |
7 |
Reset password
8 |
9 |
Sign in?
10 |
11 |
12 |
13 | @csrf
14 |
15 | @if (session()->has('invalidResetToken'))
16 |
17 | Invalid reset token.
18 |
19 | @endif
20 |
21 | @if (session()->has('passwordResetLinkSent'))
22 |
23 | We have e-mailed your password reset link!
24 |
25 | @endif
26 |
27 |
28 | E-mail address
29 |
30 |
36 |
37 | @if ($errors->has('email'))
38 |
39 | {{ $errors->first('email') }}
40 |
41 | @endif
42 |
43 |
44 |
45 | Reset
46 |
47 |
48 |
49 | Don't have an account? Sign Up
50 |
51 |
52 | @endsection
--------------------------------------------------------------------------------
/resources/views/auth/reset-password.blade.php:
--------------------------------------------------------------------------------
1 | @extends('ambulatory::layouts.auth')
2 |
3 | @section('title', 'Reset password')
4 |
5 | @section('form-content')
6 |
7 |
New password
8 |
9 |
Sign in?
10 |
11 |
12 |
13 | Copy your new password, use it for your
14 |
next login ,
15 | and then reset it.
16 |
17 |
{{$password}}
18 |
19 | @endsection
--------------------------------------------------------------------------------
/resources/views/emails/credential.blade.php:
--------------------------------------------------------------------------------
1 | Hello,
2 | Use your email to log in to {{config('app.name')}}. use this password and then reset it.
3 | Password: {{$password}}
--------------------------------------------------------------------------------
/resources/views/emails/forgot-password.blade.php:
--------------------------------------------------------------------------------
1 | Hello
2 | Please follow this link to reset your password:
3 | {{$link}}
4 | This password reset link will expire in 60 minutes.
--------------------------------------------------------------------------------
/resources/views/emails/invitation.blade.php:
--------------------------------------------------------------------------------
1 | Hello,
2 | You have been invited to {{config('app.name')}} with access as a {{$role}}.
3 | Please follow this link to accept the invitation:
4 | {{$link}}
--------------------------------------------------------------------------------
/resources/views/layouts/auth.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{-- Meta Information --}}
5 |
6 |
7 |
8 |
9 |
10 | {{ config('app.name') }} - @yield('title')
11 |
12 |
13 |
14 |
15 |
16 |
35 |
36 |
--------------------------------------------------------------------------------
/resources/views/layouts/dashboard.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{-- Meta Information --}}
5 |
6 |
7 |
8 |
9 |
10 | {{ config('app.name') }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{-- Global Ambulatory Object --}}
39 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | name('login');
5 | Route::post('/login', 'LoginController@login')->name('login.attempt');
6 |
7 | // Register new account.
8 | Route::get('/register', 'RegisterController@showRegistrationForm')->name('register');
9 | Route::post('/register', 'RegisterController@register')->name('register.attempt');
10 |
11 | // Forgot password.
12 | Route::get('/password/forgot', 'ForgotPasswordController@showResetRequestForm')->name('password.forgot');
13 | Route::post('/password/forgot', 'ForgotPasswordController@sendResetLinkEmail')->name('password.email');
14 | Route::get('/password/reset/{token}', 'ForgotPasswordController@showNewPassword')->name('password.reset');
15 |
16 | // Accept invitation.
17 | Route::get('/invitation/{token}', 'AcceptInvitationController@show')->name('accept.invitation');
18 |
--------------------------------------------------------------------------------
/routes/dashboard.php:
--------------------------------------------------------------------------------
1 | name('inbox');
5 | Route::get('/api/inbox/{id}', 'InboxController@show')->name('inbox.show');
6 |
7 | // Schedules.
8 | Route::get('/api/schedules', 'ScheduleController@index')->name('schedules');
9 | Route::post('/api/schedules', 'ScheduleController@store')->name('schedules.store');
10 | Route::get('/api/schedules/{schedule}', 'ScheduleController@show')->name('schedules.show');
11 | Route::patch('/api/schedules/{schedule}', 'ScheduleController@update')->name('schedules.update');
12 | // schedule availability.
13 | Route::post('/api/schedules/{schedule}/availability', 'ScheduleAvailabilityController@store')->name('schedules.availability');
14 |
15 | // book an appointment.
16 | Route::get('/api/booking/{schedule}/availability-slots', 'BookAppointmentController@index')->name('book.appointment');
17 | Route::post('/api/booking/{schedule}/availability-slots', 'BookAppointmentController@store')->name('book.appointment');
18 |
19 | // Doctors.
20 | Route::get('/api/doctors', 'DoctorController@index')->name('doctors');
21 | Route::get('/api/doctors/{doctor}', 'DoctorController@show')->name('doctors.show');
22 |
23 | // Medical Forms.
24 | Route::get('/api/medical-forms', 'MedicalFormController@index')->name('medical-forms');
25 | Route::post('/api/medical-forms', 'MedicalFormController@store')->name('medical-forms.store');
26 | Route::get('/api/medical-forms/{medicalForm}', 'MedicalFormController@show')->name('medical-forms.show');
27 | Route::patch('/api/medical-forms/{medicalForm}', 'MedicalFormController@update')->name('medical-forms.update');
28 |
29 | // Invitations.
30 | Route::get('/api/invitations', 'InvitationController@index')->name('invitations');
31 | Route::post('/api/invitations', 'InvitationController@store')->name('invitations.store');
32 | Route::get('/api/invitations/{invitation}', 'InvitationController@show')->name('invitations.show');
33 | Route::patch('/api/invitations/{invitation}', 'InvitationController@update')->name('invitations.update');
34 | Route::delete('/api/invitations/{invitation}', 'InvitationController@destroy')->name('invitations.destroy');
35 |
36 | // Health Facilities.
37 | Route::get('/api/health-facilities', 'HealthFacilityController@index')->name('health-facilities');
38 | Route::post('/api/health-facilities', 'HealthFacilityController@store')->name('health-facilities.store');
39 | Route::get('/api/health-facilities/{healthFacility}', 'HealthFacilityController@show')->name('health-facilities.show');
40 | Route::patch('/api/health-facilities/{healthFacility}', 'HealthFacilityController@update')->name('health-facilities.update');
41 |
42 | // Specializations.
43 | Route::get('/api/specializations', 'SpecializationController@index')->name('specializations');
44 | Route::post('/api/specializations', 'SpecializationController@store')->name('specializations.store');
45 | Route::get('/api/specializations/{specialization}', 'SpecializationController@show')->name('specializations.show');
46 | Route::patch('/api/specializations/{specialization}', 'SpecializationController@update')->name('specializations.update');
47 | Route::delete('/api/specializations/{specialization}', 'SpecializationController@destroy')->name('specializations.destroy');
48 |
49 | // Staff.
50 | Route::get('/api/staff', 'StaffController@index')->name('staff');
51 | // Availabilities.
52 | Route::patch('/api/availabilities/{availability}', 'AvailabilityController@update')->name('availabilities.update');
53 |
54 | // Settings.
55 | Route::namespace('Settings')->group(function () {
56 | // Account.
57 | Route::get('/api/account', 'AccountController@show')->name('account');
58 | Route::patch('/api/account', 'AccountController@update')->name('account');
59 | // Doctor profile.
60 | Route::get('/api/doctor-profile/{doctor}', 'DoctorProfileController@show')->name('doctor-profile.show');
61 | Route::post('/api/doctor-profile', 'DoctorProfileController@store')->name('doctor-profile.store');
62 | Route::patch('/api/doctor-profile/{doctor}', 'DoctorProfileController@update')->name('doctor-profile.update');
63 | // User Avatar.
64 | Route::post('/api/uploads-user-avatar', 'UploadUserAvatarController@create')->name('upload-user-avatar');
65 | // New password.
66 | Route::post('/api/new-password', 'NewPasswordController@update')->name('new-password');
67 | });
68 |
69 | // Logout Route.
70 | Route::get('/logout', 'Auth\LoginController@logout')->name('logout');
71 | // Catch-all Route.
72 | Route::get('/{view?}', 'AmbulatoryController')->where('view', '(.*)')->name('ambulatory');
73 |
--------------------------------------------------------------------------------
/src/Ambulatory.php:
--------------------------------------------------------------------------------
1 | DoctorPolicy::class,
21 | Schedule::class => DoctorSchedulePolicy::class,
22 | MedicalForm::class => MedicalFormPolicy::class,
23 | Availability::class => AvailabilityPolicy::class,
24 | ];
25 |
26 | /**
27 | * Register ambulatory policies.
28 | *
29 | * @return void
30 | */
31 | public function registerPolicies()
32 | {
33 | foreach ($this->policies as $key => $value) {
34 | Gate::policy($key, $value);
35 | }
36 | }
37 |
38 | /**
39 | * Get the default JavaScript variables for Ambulatory.
40 | *
41 | * @return array
42 | */
43 | public static function scriptVariables()
44 | {
45 | return [
46 | 'path' => config('ambulatory.path'),
47 | 'user' => auth('ambulatory')->check()
48 | ? auth('ambulatory')->user()->scriptVariables()
49 | : null,
50 | ];
51 | }
52 |
53 | /**
54 | * Disable wrapping of the outer-most resource by default.
55 | *
56 | * @return void
57 | */
58 | public function wrapResource()
59 | {
60 | Resource::withoutWrapping();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/AmbulatoryModel.php:
--------------------------------------------------------------------------------
1 | registerRoutes();
20 | $this->registerMigrations();
21 | $this->registerAuthGuard();
22 | $this->registerPublishing();
23 | $this->registerResourceViews();
24 |
25 | $ambulatory->registerPolicies();
26 | $ambulatory->wrapResource();
27 | }
28 |
29 | /**
30 | * Register the package routes.
31 | *
32 | * @return void
33 | */
34 | private function registerRoutes()
35 | {
36 | $path = config('ambulatory.path');
37 |
38 | Route::namespace('Ambulatory\Http\Controllers\Auth')
39 | ->middleware(['web', RedirectIfAuthenticated::class])
40 | ->as('ambulatory.')
41 | ->prefix($path)
42 | ->group(function () {
43 | $this->loadRoutesFrom(__DIR__.'/../routes/auth.php');
44 | });
45 |
46 | Route::namespace('Ambulatory\Http\Controllers')
47 | ->middleware(['web', Authenticate::class])
48 | ->as('ambulatory.')
49 | ->prefix($path)
50 | ->group(function () {
51 | $this->loadRoutesFrom(__DIR__.'/../routes/dashboard.php');
52 | });
53 | }
54 |
55 | /**
56 | * Register the package's migrations.
57 | *
58 | * @return void
59 | */
60 | private function registerMigrations()
61 | {
62 | $this->loadMigrationsFrom(__DIR__.'/Migrations');
63 | }
64 |
65 | /**
66 | * Register the package's authentication guard.
67 | *
68 | * @return void
69 | */
70 | private function registerAuthGuard()
71 | {
72 | $this->app['config']->set('auth.providers.ambulatory_users', [
73 | 'driver' => 'eloquent',
74 | 'model' => User::class,
75 | ]);
76 |
77 | $this->app['config']->set('auth.guards.ambulatory', [
78 | 'driver' => 'session',
79 | 'provider' => 'ambulatory_users',
80 | ]);
81 | }
82 |
83 | /**
84 | * Register the package's publishable resources.
85 | *
86 | * @return void
87 | */
88 | private function registerPublishing()
89 | {
90 | if ($this->app->runningInConsole()) {
91 | $this->publishes([
92 | __DIR__.'/../public' => public_path('vendor/ambulatory'),
93 | ], 'ambulatory-assets');
94 |
95 | $this->publishes([
96 | __DIR__.'/../config/ambulatory.php' => config_path('ambulatory.php'),
97 | ], 'ambulatory-config');
98 | }
99 | }
100 |
101 | /**
102 | * Register the package resource views.
103 | *
104 | * @return void
105 | */
106 | public function registerResourceViews()
107 | {
108 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'ambulatory');
109 | }
110 |
111 | /**
112 | * Register any package services.
113 | *
114 | * @return void
115 | */
116 | public function register()
117 | {
118 | $this->mergeConfigFrom(__DIR__.'/../config/ambulatory.php', 'ambulatory');
119 |
120 | $this->commands([
121 | Console\InstallCommand::class,
122 | Console\MigrateCommand::class,
123 | ]);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Availability.php:
--------------------------------------------------------------------------------
1 | 'array',
60 | ];
61 |
62 | /**
63 | * The schedule of availability.
64 | *
65 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
66 | */
67 | public function schedule()
68 | {
69 | return $this->belongsTo(Schedule::class, 'schedule_id');
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Booking.php:
--------------------------------------------------------------------------------
1 | 'boolean',
56 | ];
57 |
58 | /**
59 | * The booking schedule.
60 | *
61 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
62 | */
63 | public function schedule()
64 | {
65 | return $this->belongsTo(Schedule::class, 'schedule_id');
66 | }
67 |
68 | /**
69 | * The booking medical form.
70 | *
71 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
72 | */
73 | public function medicalForm()
74 | {
75 | return $this->belongsTo(MedicalForm::class, 'medical_form_id');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Console/InstallCommand.php:
--------------------------------------------------------------------------------
1 | comment('Publishing Ambulatory Assets...');
31 | $this->callSilent('vendor:publish', ['--tag' => 'ambulatory-assets']);
32 |
33 | $this->comment('Publishing Ambulatory Configuration...');
34 | $this->callSilent('vendor:publish', ['--tag' => 'ambulatory-config']);
35 |
36 | $this->info('Ambulatory was installed successfully.');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Console/MigrateCommand.php:
--------------------------------------------------------------------------------
1 | hasTable('ambulatory_users') ||
36 | ! User::count();
37 |
38 | $this->call('migrate', [
39 | '--database' => config('ambulatory.database_connection'),
40 | '--path' => 'vendor/ambulatory/ambulatory/src/Migrations',
41 | ]);
42 |
43 | if ($shouldCreateNewUser) {
44 | User::create([
45 | 'id' => (string) Str::uuid(),
46 | 'name' => 'John Doe',
47 | 'email' => 'admin@mail.com',
48 | 'type' => User::ADMIN,
49 | 'password' => Hash::make($password = Str::random()),
50 | ]);
51 |
52 | $this->line('');
53 | $this->line('Database migrations installed successfully.');
54 | $this->line('You may log in using admin@mail.com and password: '.$password.' ');
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Doctor.php:
--------------------------------------------------------------------------------
1 | 'boolean',
65 | ];
66 |
67 | /**
68 | * The attributes excluded from the model's JSON form.
69 | *
70 | * @var array
71 | */
72 | protected $hidden = [
73 | 'working_hours_rule',
74 | ];
75 |
76 | /**
77 | * Get the fields for generating the slug.
78 | *
79 | * @var array
80 | */
81 | protected static $slugFieldsFrom = ['full_name'];
82 |
83 | /**
84 | * The specializations the doctor belongs to.
85 | *
86 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
87 | */
88 | public function specializations()
89 | {
90 | return $this->belongsToMany(Specialization::class, 'ambulatory_doctors_specializations', 'doctor_id', 'specialization_id');
91 | }
92 |
93 | /**
94 | * Doctor account.
95 | *
96 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
97 | */
98 | public function user()
99 | {
100 | return $this->belongsTo(User::class, 'user_id');
101 | }
102 |
103 | /**
104 | * Doctor schedules.
105 | *
106 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
107 | */
108 | public function schedules()
109 | {
110 | return $this->hasMany(Schedule::class, 'doctor_id');
111 | }
112 |
113 | /**
114 | * Appointments for the doctor.
115 | *
116 | * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
117 | */
118 | public function appointments()
119 | {
120 | return $this->hasManyThrough(Booking::class, Schedule::class);
121 | }
122 |
123 | /**
124 | * The working hours of a doctor.
125 | *
126 | * @return array
127 | */
128 | public function workingHours()
129 | {
130 | $rfc = new RRule($this->working_hours_rule);
131 |
132 | $rule = $rfc->getRule();
133 |
134 | return collect(explode(',', $rule['BYDAY']))->map(function ($day) use ($rule) {
135 | return [
136 | 'type' => 'wday',
137 | 'intervals' => [
138 | 'from' => date_format($rule['DTSTART'], 'H:i'),
139 | 'to' => date_format($rule['UNTIL'], 'H:i'),
140 | ],
141 | 'wday' => $day,
142 | ];
143 | })->toArray();
144 | }
145 |
146 | /**
147 | * The working hour slot of a doctor.
148 | *
149 | * @param string $incomingDate
150 | * @return array
151 | */
152 | public function workingHourSlots($incomingDate)
153 | {
154 | $date = Carbon::parse($incomingDate);
155 |
156 | $availability = Arr::where($this->workingHours(), function ($value) use ($date) {
157 | return $value['wday'] === Str::upper(Str::limit($date->format('l'), 2, ''));
158 | });
159 |
160 | return Arr::collapse($availability);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/HasSlug.php:
--------------------------------------------------------------------------------
1 | addSlug();
16 | });
17 | }
18 |
19 | /**
20 | * Add slug to the model.
21 | */
22 | protected function addSlug()
23 | {
24 | $slugSource = $this->generateSlugFrom();
25 |
26 | $slug = $this->generateUniqueSlug($slugSource);
27 |
28 | $this->slug = $slug;
29 | }
30 |
31 | /**
32 | * Generate slug from slug fields.
33 | *
34 | * @return string
35 | */
36 | protected function generateSlugFrom()
37 | {
38 | $slugFields = static::$slugFieldsFrom;
39 |
40 | $slugSource = collect($slugFields)
41 | ->map(function (string $fieldName) {
42 | return data_get($this, $fieldName, '');
43 | })
44 | ->implode('-');
45 |
46 | return substr($slugSource, 0, 100);
47 | }
48 |
49 | /**
50 | * Generate unique slug.
51 | *
52 | * @param string $value
53 | * @return string
54 | */
55 | protected function generateUniqueSlug(string $value)
56 | {
57 | $slug = $originalSlug = Str::slug($value);
58 | $i = 0;
59 |
60 | while ($this->slugExists($slug, $this->exists ? $this->id : null)) {
61 | $slug = $originalSlug.'-'.$i++;
62 | }
63 |
64 | return $slug;
65 | }
66 |
67 | /**
68 | * Find slug.
69 | *
70 | * @param string $slug
71 | * @param string $ignoreId
72 | * @return bool
73 | */
74 | protected function slugExists(string $slug, string $ignoreId = null)
75 | {
76 | $query = $this->where('slug', $slug);
77 |
78 | if ($ignoreId) {
79 | $query->where('id', '!=', $ignoreId);
80 | }
81 |
82 | return $query->exists();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/HasUuid.php:
--------------------------------------------------------------------------------
1 | getKey()) {
16 | $model->{$model->getKeyName()} = (string) Str::uuid();
17 | }
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/HealthFacility.php:
--------------------------------------------------------------------------------
1 | Ambulatory::scriptVariables(),
18 | ]);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Auth/AcceptInvitationController.php:
--------------------------------------------------------------------------------
1 | firstOrFail(), function ($invitation) {
18 | $invitation->accepted();
19 |
20 | $invitation->delete();
21 | });
22 |
23 | return redirect()->route('ambulatory.login')->with('invitationAccepted', true);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Auth/ForgotPasswordController.php:
--------------------------------------------------------------------------------
1 | all(), [
33 | 'email' => 'required|email',
34 | ])->validate();
35 |
36 | if ($user = User::whereEmail(request('email'))->first()) {
37 | cache(['password.reset.'.$user->id => $token = Str::random()],
38 | now()->addMinutes(60)
39 | );
40 |
41 | Mail::to($user->email)->send(new ResetPasswordEmail(
42 | encrypt($user->id.'|'.$token)
43 | ));
44 | }
45 |
46 | return redirect()->route('ambulatory.password.forgot')->with('passwordResetLinkSent', true);
47 | }
48 |
49 | /**
50 | * Show the new password to the user.
51 | *
52 | * @param string $token
53 | * @return \Illuminate\Http\Response
54 | */
55 | public function showNewPassword($token)
56 | {
57 | try {
58 | $token = decrypt($token);
59 |
60 | [$userId, $token] = explode('|', $token);
61 |
62 | $user = User::findOrFail($userId);
63 | } catch (Throwable $e) {
64 | return redirect()->route('ambulatory.password.forgot')->with('invalidResetToken', true);
65 | }
66 |
67 | if (cache('password.reset.'.$userId) != $token) {
68 | return redirect()->route('ambulatory.password.forgot')->with('invalidResetToken', true);
69 | }
70 |
71 | cache()->forget('password.reset.'.$userId);
72 |
73 | $user->password = Hash::make($password = Str::random());
74 |
75 | $user->save();
76 |
77 | return view('ambulatory::auth.reset-password', [
78 | 'password' => $password,
79 | ]);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Auth/LoginController.php:
--------------------------------------------------------------------------------
1 | guard()->logout();
43 |
44 | $request->session()->invalidate();
45 |
46 | return redirect()->route('ambulatory.login')->with('loggedOut', true);
47 | }
48 |
49 | /**
50 | * Get the guard to be used during authentication.
51 | *
52 | * @return \Illuminate\Contracts\Auth\StatefulGuard
53 | */
54 | protected function guard()
55 | {
56 | return Auth::guard('ambulatory');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'max:255'],
37 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:ambulatory_users'],
38 | 'password' => ['required', 'string', 'min:6', 'confirmed'],
39 | ]);
40 | }
41 |
42 | /**
43 | * Create a new user instance after a valid registration.
44 | *
45 | * @param array $data
46 | * @return \Ambulatory\Ambulatory\User
47 | */
48 | protected function create(array $data)
49 | {
50 | return User::create([
51 | 'id' => Str::uuid(),
52 | 'name' => $data['name'],
53 | 'email' => $data['email'],
54 | 'password' => Hash::make($data['password']),
55 | ]);
56 | }
57 |
58 | /**
59 | * Where to redirect users after register.
60 | *
61 | * @return string
62 | */
63 | public function redirectPath()
64 | {
65 | return '/'.config('ambulatory.path');
66 | }
67 |
68 | /**
69 | * Get the guard to be used during registration.
70 | *
71 | * @return \Illuminate\Contracts\Auth\StatefulGuard
72 | */
73 | protected function guard()
74 | {
75 | return Auth::guard('ambulatory');
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Http/Controllers/AvailabilityController.php:
--------------------------------------------------------------------------------
1 | middleware(VerifiedDoctor::class);
18 | }
19 |
20 | /**
21 | * Update the specified availability in storage.
22 | *
23 | * @param Availability $availability
24 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
25 | */
26 | public function update(AvailabilityRequest $request, Availability $availability)
27 | {
28 | $this->authorize('manage', $availability);
29 |
30 | $availability->update($request->validated());
31 |
32 | return new AvailabilityResource($availability);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Controllers/BookAppointmentController.php:
--------------------------------------------------------------------------------
1 | json([
22 | 'data' => $schedule->availabilitySlots($date),
23 | ]);
24 | }
25 |
26 | /**
27 | * Store a newly created booking in storage.
28 | *
29 | * @param BookAppointmentRequest $request
30 | * @param Schedule $schedule
31 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
32 | */
33 | public function store(BookAppointmentRequest $request, Schedule $schedule)
34 | {
35 | $booking = $schedule->bookings()->create($request->validated());
36 |
37 | return new BookingResource($booking);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | latest()->paginate(25);
18 |
19 | return DoctorResource::collection($doctors);
20 | }
21 |
22 | /**
23 | * Display the specified doctor.
24 | *
25 | * @param Doctor $doctor
26 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
27 | */
28 | public function show(Doctor $doctor)
29 | {
30 | return new DoctorResource($doctor->load('user', 'specializations', 'schedules.healthFacility'));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Http/Controllers/HealthFacilityController.php:
--------------------------------------------------------------------------------
1 | middleware(Admin::class)->except('index');
18 | }
19 |
20 | /**
21 | * Display a listing of the health facilities.
22 | *
23 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
24 | */
25 | public function index()
26 | {
27 | $healthFacilities = HealthFacility::latest()->paginate(25);
28 |
29 | return HealthFacilityResource::collection($healthFacilities);
30 | }
31 |
32 | /**
33 | * Store a newly created health facility in storage.
34 | *
35 | * @param HealthFacilityRequest $request
36 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
37 | */
38 | public function store(HealthFacilityRequest $request)
39 | {
40 | $healthFacility = HealthFacility::create($request->validated());
41 |
42 | return new HealthFacilityResource($healthFacility);
43 | }
44 |
45 | /**
46 | * Display the specified medical form.
47 | *
48 | * @param HealthFacility $healthFacility
49 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
50 | */
51 | public function show(HealthFacility $healthFacility)
52 | {
53 | return new HealthFacilityResource($healthFacility);
54 | }
55 |
56 | /**
57 | * Update the specified health facility in storage.
58 | *
59 | * @param HealthFacilityRequest $request
60 | * @param HealthFacility $healthFacility
61 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
62 | */
63 | public function update(HealthFacilityRequest $request, HealthFacility $healthFacility)
64 | {
65 | $healthFacility->update($request->validated());
66 |
67 | return new HealthFacilityResource($healthFacility);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Http/Controllers/InboxController.php:
--------------------------------------------------------------------------------
1 | user()
17 | ->inbox()
18 | ->with('schedule.doctor', 'schedule.healthFacility')
19 | ->paginate(25);
20 |
21 | return BookingResource::collection($bookings);
22 | }
23 |
24 | /**
25 | * Show the appointment to the user inbox.
26 | *
27 | * @param string $id
28 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
29 | */
30 | public function show($id)
31 | {
32 | $booking = auth('ambulatory')->user()
33 | ->inbox()
34 | ->with('schedule.doctor', 'schedule.healthFacility', 'medicalForm')
35 | ->findOrFail($id);
36 |
37 | return new BookingResource($booking);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Http/Controllers/InvitationController.php:
--------------------------------------------------------------------------------
1 | middleware(Admin::class);
18 | }
19 |
20 | /**
21 | * Display a listing of the invitations.
22 | *
23 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
24 | */
25 | public function index()
26 | {
27 | $invitations = Invitation::latest()->paginate(25);
28 |
29 | return InvitationResource::collection($invitations);
30 | }
31 |
32 | /**
33 | * Store a newly created invitation in storage.
34 | *
35 | * @param InvitationRequest $request
36 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
37 | */
38 | public function store(InvitationRequest $request)
39 | {
40 | $invitation = Invitation::create($request->validatedFields());
41 |
42 | return new InvitationResource($invitation);
43 | }
44 |
45 | /**
46 | * Display the specified invitation.
47 | *
48 | * @param Invitation $invitation
49 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
50 | */
51 | public function show(Invitation $invitation)
52 | {
53 | return new InvitationResource($invitation);
54 | }
55 |
56 | /**
57 | * Update the specified invitation in storage.
58 | *
59 | * @param InvitationRequest $request
60 | * @param Invitation $invitation
61 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
62 | */
63 | public function update(InvitationRequest $request, Invitation $invitation)
64 | {
65 | $invitation->update($request->validatedFields());
66 |
67 | return new InvitationResource($invitation);
68 | }
69 |
70 | /**
71 | * Remove the specified invitation from storage.
72 | *
73 | * @param Invitation $invitation
74 | * @return \Illuminate\Http\Response
75 | */
76 | public function destroy(Invitation $invitation)
77 | {
78 | $invitation->delete();
79 |
80 | return response()->json(null, 204);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Http/Controllers/MedicalFormController.php:
--------------------------------------------------------------------------------
1 | id())->paginate(25);
19 |
20 | return MedicalFormResource::collection($medicalForms);
21 | }
22 |
23 | /**
24 | * Store a newly created medical form in storage.
25 | *
26 | * @param MedicalFormRequest $request
27 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
28 | */
29 | public function store(MedicalFormRequest $request)
30 | {
31 | $medicalForm = MedicalForm::create($request->validated() + ['user_id' => auth('ambulatory')->id()]);
32 |
33 | return new MedicalFormResource($medicalForm);
34 | }
35 |
36 | /**
37 | * Display the specified medical form.
38 | *
39 | * @param MedicalForm $medicalForm
40 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
41 | */
42 | public function show(MedicalForm $medicalForm)
43 | {
44 | $this->authorize('manage', $medicalForm);
45 |
46 | return new MedicalFormResource($medicalForm->load('user'));
47 | }
48 |
49 | /**
50 | * Update the specified medical form in storage.
51 | *
52 | * @param MedicalFormRequest $request
53 | * @param MedicalForm $medicalForm
54 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
55 | */
56 | public function update(MedicalFormRequest $request, MedicalForm $medicalForm)
57 | {
58 | $this->authorize('manage', $medicalForm);
59 |
60 | $medicalForm->update($request->validated());
61 |
62 | return new MedicalFormResource($medicalForm);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ScheduleAvailabilityController.php:
--------------------------------------------------------------------------------
1 | middleware(VerifiedDoctor::class)->except('index');
18 | }
19 |
20 | /**
21 | * Display a listing of the availability of schedule.
22 | *
23 | * @param Schedule $schedule
24 | * @return \Illuminate\Http\JsonResponse
25 | */
26 | public function index(Schedule $schedule)
27 | {
28 | // @todo Custom schedule availability.
29 | }
30 |
31 | /**
32 | * Store a newly created schedule availability in storage.
33 | *
34 | * @param ScheduleAvailabilityRequest $request
35 | * @param Schedule $schedule
36 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
37 | */
38 | public function store(ScheduleAvailabilityRequest $request, Schedule $schedule)
39 | {
40 | $this->authorize('manage', $schedule);
41 |
42 | $availability = $schedule->addCustomAvailability($request->validated());
43 |
44 | return new AvailabilityResource($availability);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ScheduleController.php:
--------------------------------------------------------------------------------
1 | middleware(VerifiedDoctor::class);
18 | }
19 |
20 | /**
21 | * Display a listing of the schedules.
22 | *
23 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
24 | */
25 | public function index()
26 | {
27 | $schedules = Schedule::with('healthFacility')
28 | ->where('doctor_id', auth('ambulatory')->user()->doctorProfile->id)
29 | ->latest()
30 | ->paginate(25);
31 |
32 | return ScheduleResource::collection($schedules);
33 | }
34 |
35 | /**
36 | * Store a newly created schedule in storage.
37 | *
38 | * @param ScheduleRequest $request
39 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
40 | */
41 | public function store(ScheduleRequest $request)
42 | {
43 | $schedule = Schedule::create($request->validatedFields());
44 |
45 | return new ScheduleResource($schedule);
46 | }
47 |
48 | /**
49 | * Display the specified schedule.
50 | *
51 | * @param Schedule $schedule
52 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
53 | */
54 | public function show(Schedule $schedule)
55 | {
56 | $this->authorize('manage', $schedule);
57 |
58 | return new ScheduleResource($schedule->load('healthFacility'));
59 | }
60 |
61 | /**
62 | * Update the specified schedule in storage.
63 | *
64 | * @param ScheduleRequest $request
65 | * @param Schedule $schedule
66 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
67 | */
68 | public function update(ScheduleRequest $request, Schedule $schedule)
69 | {
70 | $this->authorize('manage', $schedule);
71 |
72 | $schedule->update($request->validatedFields());
73 |
74 | return new ScheduleResource($schedule);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Settings/AccountController.php:
--------------------------------------------------------------------------------
1 | user());
18 | }
19 |
20 | /**
21 | * Update the user account.
22 | *
23 | * @param AccountRequest $request
24 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
25 | */
26 | public function update(AccountRequest $request)
27 | {
28 | auth('ambulatory')->user()->update($request->validated());
29 |
30 | return new UserResource(auth('ambulatory')->user());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Settings/DoctorProfileController.php:
--------------------------------------------------------------------------------
1 | middleware(AmbulatoryDoctor::class);
20 | }
21 |
22 | /**
23 | * Get doctors' profile.
24 | *
25 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
26 | */
27 | public function show(Doctor $doctor)
28 | {
29 | $this->authorize('update', $doctor);
30 |
31 | return new DoctorResource($doctor->load('specializations'));
32 | }
33 |
34 | /**
35 | * Store doctors' profile.
36 | *
37 | * @param DoctorProfileRequest $request
38 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
39 | */
40 | public function store(DoctorProfileRequest $request)
41 | {
42 | $doctor = Doctor::create($request->validatedFields() + ['user_id' => auth('ambulatory')->id()]);
43 |
44 | $doctor->specializations()->sync(
45 | $this->specializations(request('specializations'))
46 | );
47 |
48 | return new DoctorResource($doctor->load('specializations'));
49 | }
50 |
51 | /**
52 | * Update doctors' profile.
53 | *
54 | * @param DoctorProfileRequest $request
55 | * @param Doctor $doctor
56 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
57 | */
58 | public function update(DoctorProfileRequest $request, Doctor $doctor)
59 | {
60 | $this->authorize('update', $doctor);
61 |
62 | $doctor->update($request->validatedFields());
63 |
64 | $doctor->specializations()->sync(
65 | $this->specializations(request('specializations'))
66 | );
67 |
68 | return new DoctorResource($doctor->load('specializations'));
69 | }
70 |
71 | protected function specializations($specializations)
72 | {
73 | $allSpecializations = Specialization::all();
74 |
75 | return collect($specializations)->map(function ($specialization) use ($allSpecializations) {
76 | $speciality = $allSpecializations->where('id', $specialization['id'])->first();
77 |
78 | return (string) $speciality->id;
79 | })->toArray();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Settings/NewPasswordController.php:
--------------------------------------------------------------------------------
1 | user()->update([
20 | 'password' => Hash::make($request->new_password),
21 | ]);
22 |
23 | return response()->json(null, 204);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Settings/UploadUserAvatarController.php:
--------------------------------------------------------------------------------
1 | image->store('/public/ambulatory/images', config('ambulatory.storage_disk'))
16 | );
17 |
18 | return response()->json([
19 | 'url' => '/'.$path,
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Http/Controllers/SpecializationController.php:
--------------------------------------------------------------------------------
1 | middleware(Admin::class)->except('index');
18 | }
19 |
20 | /**
21 | * Display a listing of the specializations.
22 | *
23 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
24 | */
25 | public function index()
26 | {
27 | $specializations = Specialization::latest()->paginate(25);
28 |
29 | return SpecializationResource::collection($specializations);
30 | }
31 |
32 | /**
33 | * Store a newly created specialization in storage.
34 | *
35 | * @param SpecializationRequest $request
36 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
37 | */
38 | public function store(SpecializationRequest $request)
39 | {
40 | $specialization = Specialization::create($request->validated());
41 |
42 | return new SpecializationResource($specialization);
43 | }
44 |
45 | /**
46 | * Display the specified specialization.
47 | *
48 | * @param Specialization $specialization
49 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
50 | */
51 | public function show(Specialization $specialization)
52 | {
53 | return new SpecializationResource($specialization);
54 | }
55 |
56 | /**
57 | * Update the specified specialization in storage.
58 | *
59 | * @param SpecializationRequest $request
60 | * @param Specialization $specialization
61 | * @return \Illuminate\Http\Resources\Json\JsonResource|\Illuminate\Http\JsonResponse
62 | */
63 | public function update(SpecializationRequest $request, Specialization $specialization)
64 | {
65 | $specialization->update($request->validated());
66 |
67 | return new SpecializationResource($specialization);
68 | }
69 |
70 | /**
71 | * Remove the specified specialization from storage.
72 | *
73 | * @param Specialization $specialization
74 | * @return \Illuminate\Http\JsonResponse
75 | */
76 | public function destroy(Specialization $specialization)
77 | {
78 | $specialization->delete();
79 |
80 | return response()->json(null, 204);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Http/Controllers/StaffController.php:
--------------------------------------------------------------------------------
1 | middleware(Admin::class);
17 | }
18 |
19 | /**
20 | * Display a listing of the staff.
21 | *
22 | * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
23 | */
24 | public function index()
25 | {
26 | $staff = User::whereNotIn('type', [User::PATIENT])->latest()->paginate(25);
27 |
28 | return UserResource::collection($staff);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Middleware/Admin.php:
--------------------------------------------------------------------------------
1 | user()->isAdmin()) {
23 | return $next($request);
24 | }
25 |
26 | throw new HttpException(403, 'Sorry, you are forbidden from accessing this resources.');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
28 | }
29 |
30 | /**
31 | * Handle an incoming request.
32 | *
33 | * @param \Illuminate\Http\Request $request
34 | * @param \Closure $next
35 | * @return mixed
36 | *
37 | * @throws \Illuminate\Auth\AuthenticationException
38 | */
39 | public function handle(Request $request, Closure $next)
40 | {
41 | if ($this->auth->guard('ambulatory')->check()) {
42 | $this->auth->shouldUse('ambulatory');
43 | } else {
44 | throw new AuthenticationException(
45 | 'Unauthenticated.', ['ambulatory'], route('ambulatory.login')
46 | );
47 | }
48 |
49 | return $next($request);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Http/Middleware/Doctor.php:
--------------------------------------------------------------------------------
1 | user()->isDoctor()) {
22 | return $next($request);
23 | }
24 |
25 | return abort(403, 'Sorry, you are forbidden from accessing this resources.');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Http/Middleware/RedirectIfAuthenticated.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
27 | }
28 |
29 | /**
30 | * Handle an incoming request.
31 | *
32 | * @param \Illuminate\Http\Request $request
33 | * @param \Closure $next
34 | * @return mixed
35 | *
36 | * @throws \Illuminate\Auth\AuthenticationException
37 | */
38 | public function handle(Request $request, Closure $next)
39 | {
40 | if ($this->auth->guard('ambulatory')->check()) {
41 | return redirect('/'.config('ambulatory.path'));
42 | }
43 |
44 | return $next($request);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Http/Middleware/VerifiedDoctor.php:
--------------------------------------------------------------------------------
1 | user()->isVerifiedDoctor()) {
22 | return $next($request);
23 | }
24 |
25 | return abort(403, 'Please verify your doctor profile first');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Http/Requests/AccountRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string',
29 | 'name' => 'required|string|min:3',
30 | 'email' => [
31 | 'required',
32 | 'email',
33 | Rule::unique(config('ambulatory.database_connection').'.ambulatory_users', 'email')
34 | ->ignore(auth('ambulatory')->id()),
35 | ],
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Http/Requests/AvailabilityRequest.php:
--------------------------------------------------------------------------------
1 | 'nullable|array',
28 | 'date' => 'required|date',
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Requests/BookAppointmentRequest.php:
--------------------------------------------------------------------------------
1 | route('schedule');
29 |
30 | return [
31 | 'preferred_date_time' => [
32 | 'bail',
33 | 'required',
34 | 'date',
35 | 'after_or_equal:'.$schedule->start_date->toDateTimeString(),
36 | 'before_or_equal:'.$schedule->end_date->toDateTimeString(),
37 | Rule::unique(config('ambulatory.database_connection').'.ambulatory_bookings', 'preferred_date_time')
38 | ->where('schedule_id', $schedule->id),
39 | new BookingAvailabilityRule($schedule),
40 | ],
41 | 'medical_form_id' => [
42 | 'bail',
43 | 'required',
44 | 'string',
45 | Rule::exists(config('ambulatory.database_connection').'.ambulatory_medical_forms', 'id')
46 | ->where('user_id', auth('ambulatory')->id()),
47 | ],
48 | 'description' => 'nullable|string',
49 | ];
50 | }
51 |
52 | /**
53 | * Get custom attributes for validator errors.
54 | *
55 | * @return array
56 | */
57 | public function attributes()
58 | {
59 | return [
60 | 'medical_form_id' => 'medical form',
61 | ];
62 | }
63 |
64 | /**
65 | * Get the error messages for the defined validation rules.
66 | *
67 | * @return array
68 | */
69 | public function messages()
70 | {
71 | return [
72 | 'preferred_date_time.unique' => 'The preferred date time has already been booked.',
73 | ];
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Http/Requests/DoctorProfileRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string|max:255',
30 | 'qualification' => 'required|string|max:255',
31 | 'practicing_from' => 'required|date|max:255',
32 | 'professional_statement' => 'nullable|string|min:2|max:255',
33 | 'specializations' => 'required|array',
34 | 'specializations.*.id' => Rule::exists(config('ambulatory.database_connection').'.ambulatory_specializations', 'id'),
35 | ];
36 | }
37 |
38 | /**
39 | * Set the validated fields request that apply to the model.
40 | *
41 | * @return array
42 | */
43 | public function validatedFields()
44 | {
45 | return [
46 | 'full_name' => $this->full_name,
47 | 'qualification' => $this->qualification,
48 | 'practicing_from' => $this->practicing_from,
49 | 'professional_statement' => $this->professional_statement,
50 | 'working_hours_rule' => $this->setDefaultWorkingHours(),
51 | ];
52 | }
53 |
54 | /**
55 | * Set the default working hours rule.
56 | *
57 | * @return string
58 | */
59 | protected function setDefaultWorkingHours()
60 | {
61 | $rule = new RRule([
62 | 'freq' => 'daily',
63 | 'byday' => 'MO,TU,WE,TH,FR',
64 | 'dtstart' => today()->createFromTime(9, 0, 0),
65 | 'until' => today()->createFromTime(17, 00, 00),
66 | ]);
67 |
68 | return $rule->rfcString();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Http/Requests/HealthFacilityRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string|min:2|max:255',
28 | 'address' => 'required|string|min:2|max:255',
29 | 'city' => 'required|string|min:2|max:255',
30 | 'state' => 'nullable|string|min:2|max:255',
31 | 'country' => 'required|string|max:255',
32 | 'zip_code' => 'required|string|min:2|max:255',
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Http/Requests/InvitationRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string|email|max:255|unique:ambulatory_invitations|unique:ambulatory_users',
29 | 'role' => 'required|string',
30 | ];
31 | }
32 |
33 | /**
34 | * Set the validated fields request that apply to the model.
35 | *
36 | * @return array
37 | */
38 | public function validatedFields()
39 | {
40 | return [
41 | 'email' => $this->email,
42 | 'role' => $this->role,
43 | 'token' => Str::limit(md5($this->email.Str::random()), 25, ''),
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Http/Requests/MedicalFormRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string|min:2|max:225',
28 | 'full_name' => 'required|string|min:2|max:225',
29 | 'dob' => 'required|date',
30 | 'gender' => 'required|string|min:2|max:225',
31 | 'address' => 'required|string|min:2|max:225',
32 | 'city' => 'required|string|min:2|max:225',
33 | 'state' => 'nullable|string|min:2|max:225',
34 | 'zip_code' => 'required|string|min:2|max:225',
35 | 'home_phone' => 'nullable|string|min:2|max:225',
36 | 'cell_phone' => 'required|string|min:2|max:225',
37 | 'marital_status' => 'required|string|min:2|max:225',
38 | ];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Http/Requests/NewPasswordRequest.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'min:8', new CurrentPassRule],
29 | 'new_password' => 'required|string|min:8',
30 | 'confirm_new_password' => 'required|string|min:8|same:new_password',
31 | ];
32 | }
33 |
34 | /**
35 | * Get the error messages for the defined validation rules.
36 | *
37 | * @return array
38 | */
39 | public function messages()
40 | {
41 | return [
42 | 'confirm_new_password.same' => 'Password confirmation should match to the new password',
43 | ];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Http/Requests/ScheduleAvailabilityRequest.php:
--------------------------------------------------------------------------------
1 | 'required|array',
28 | 'intervals.*.from' => 'required|string',
29 | 'intervals.*.to' => 'required|string',
30 | 'date' => 'required|date',
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Requests/ScheduleRequest.php:
--------------------------------------------------------------------------------
1 | 'required|array',
30 | 'health_facility.*id' => [
31 | 'bail',
32 | 'required',
33 | Rule::exists(config('ambulatory.database_connection').'.ambulatory_health_facilities', 'id'),
34 | Rule::unique(config('ambulatory.database_connection').'.ambulatory_schedules', 'health_facility_id')
35 | ->ignore($this->id)
36 | ->where('doctor_id', auth('ambulatory')->user()->doctorProfile->id),
37 | ],
38 | 'start_date' => [
39 | 'bail',
40 | 'required',
41 | 'date',
42 | 'after_or_equal:'.today()->toDateString(),
43 | ],
44 | 'end_date' => 'bail|required|date|after:start_date',
45 | 'service_time' => 'nullable|integer',
46 | ];
47 | }
48 |
49 | /**
50 | * Set the validated fields request that apply to the model.
51 | *
52 | * @return array
53 | */
54 | public function validatedFields()
55 | {
56 | $serviceTime = $this->service_time === null
57 | ? Schedule::ESTIMATED_SERVICE_TIME
58 | : $this->service_time;
59 |
60 | return [
61 | 'doctor_id' => auth('ambulatory')->user()->doctorProfile->id,
62 | 'health_facility_id' => $this->health_facility['id'],
63 | 'start_date' => $this->start_date,
64 | 'end_date' => $this->end_date,
65 | 'estimated_service_time_in_minutes' => $serviceTime,
66 | ];
67 | }
68 |
69 | /**
70 | * Get custom attributes for validator errors.
71 | *
72 | * @return array
73 | */
74 | public function attributes()
75 | {
76 | return [
77 | 'health_facility.id' => 'Health facility',
78 | ];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Http/Requests/SpecializationRequest.php:
--------------------------------------------------------------------------------
1 | 'required|string|min:2|max:255',
28 | 'description' => 'nullable|string|min:2|max:255',
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Resources/AvailabilityResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'type' => $this->type,
20 | 'intervals' => $this->intervals,
21 | 'date' => $this->date,
22 | 'created_at' => $this->created_at,
23 | 'updated_at' => $this->updated_at,
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Http/Resources/BookingResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'preferred_date_time' => $this->preferred_date_time,
20 | 'actual_end_date_time' => $this->actual_end_date_time,
21 | 'description' => $this->description,
22 | 'is_active' => (bool) $this->is_active,
23 | 'medical_form' => new MedicalFormResource($this->whenLoaded('medicalForm')),
24 | 'doctor_schedule' => new ScheduleResource($this->whenLoaded('schedule')),
25 | 'created_at' => $this->created_at,
26 | 'updated_at' => $this->updated_at,
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Http/Resources/DoctorResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'full_name' => $this->full_name,
20 | 'slug' => $this->slug,
21 | 'qualification' => $this->qualification,
22 | 'practicing_from' => $this->practicing_from,
23 | 'professional_statement' => $this->professional_statement,
24 | 'is_active' => (bool) $this->is_active,
25 | 'account' => new UserResource($this->whenLoaded('user')),
26 | 'schedules' => ScheduleResource::collection($this->whenLoaded('schedules')),
27 | 'specializations' => SpecializationResource::collection($this->whenLoaded('specializations')),
28 | 'created_at' => $this->created_at,
29 | 'updated_at' => $this->updated_at,
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Http/Resources/HealthFacilityResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'name' => $this->name,
20 | 'slug' => $this->slug,
21 | 'address' => $this->address,
22 | 'city' => $this->city,
23 | 'state' => $this->state,
24 | 'country' => $this->country,
25 | 'zip_code' => $this->zip_code,
26 | 'created_at' => $this->created_at,
27 | 'updated_at' => $this->updated_at,
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Resources/InvitationResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'email' => $this->email,
20 | 'role' => $this->role,
21 | 'created_at' => $this->created_at,
22 | 'updated_at' => $this->updated_at,
23 | ];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Resources/MedicalFormResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'slug' => $this->slug,
20 | 'form_name' => $this->form_name,
21 | 'full_name' => $this->full_name,
22 | 'dob' => $this->dob,
23 | 'gender' => $this->gender,
24 | 'address' => $this->address,
25 | 'city' => $this->city,
26 | 'state' => $this->state,
27 | 'zip_code' => $this->zip_code,
28 | 'home_phone' => $this->home_phone,
29 | 'cell_phone' => $this->cell_phone,
30 | 'marital_status' => $this->marital_status,
31 | 'verified_at' => $this->verified_at,
32 | 'account' => new UserResource($this->whenLoaded('user')),
33 | 'created_at' => $this->created_at,
34 | 'updated_at' => $this->updated_at,
35 | ];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Http/Resources/ScheduleResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'start_date' => $this->start_date,
20 | 'end_date' => $this->end_date,
21 | 'estimated_service_time_in_minutes' => $this->estimated_service_time_in_minutes,
22 | 'doctor' => new DoctorResource($this->whenLoaded('doctor')),
23 | 'health_facility' => new HealthFacilityResource($this->whenLoaded('healthFacility')),
24 | 'created_at' => $this->created_at,
25 | 'updated_at' => $this->updated_at,
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Http/Resources/SpecializationResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'name' => $this->name,
20 | 'slug' => $this->slug,
21 | 'description' => $this->description,
22 | 'created_at' => $this->created_at,
23 | 'updated_at' => $this->updated_at,
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Http/Resources/UserResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'name' => $this->name,
20 | 'email' => $this->when($this->id === auth('ambulatory')->id(), $this->email),
21 | 'avatar' => $this->avatar,
22 | 'role' => $this->role,
23 | 'created_at' => $this->created_at,
24 | 'updated_at' => $this->updated_at,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Invitation.php:
--------------------------------------------------------------------------------
1 | email)->send(new InvitationEmail($invitation));
66 | });
67 | }
68 |
69 | /**
70 | * Accept the invitation.
71 | *
72 | * @return mixed
73 | */
74 | public function accepted()
75 | {
76 | $credential = Str::random();
77 |
78 | tap($this->createNewUser($credential), function ($user) use ($credential) {
79 | Mail::to($user->email)->send(new CredentialEmail($credential));
80 | });
81 | }
82 |
83 | /**
84 | * Create a new user.
85 | *
86 | * @param string $credential
87 | * @return User
88 | */
89 | protected function createNewUser(string $credential)
90 | {
91 | return User::create([
92 | 'id' => Str::uuid(),
93 | 'name' => $this->email,
94 | 'email' => $this->email,
95 | 'password' => Hash::make($credential),
96 | 'type' => $this->findUserType(),
97 | ]);
98 | }
99 |
100 | /**
101 | * Get the user type.
102 | */
103 | protected function findUserType()
104 | {
105 | if ($this->role === 'admin') {
106 | return User::ADMIN;
107 | }
108 |
109 | return User::DOCTOR;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Mail/CredentialEmail.php:
--------------------------------------------------------------------------------
1 | password = $password;
23 | }
24 |
25 | /**
26 | * Build the message.
27 | *
28 | * @return $this
29 | */
30 | public function build()
31 | {
32 | return $this->subject(config('app.name').' [credential]')
33 | ->view('ambulatory::emails.credential', [
34 | 'password' => $this->password,
35 | ]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Mail/InvitationEmail.php:
--------------------------------------------------------------------------------
1 | invitation = $invitation;
24 | }
25 |
26 | /**
27 | * Build the message.
28 | *
29 | * @return $this
30 | */
31 | public function build()
32 | {
33 | return $this->subject(config('app.name').': Invitation')
34 | ->view('ambulatory::emails.invitation', [
35 | 'link' => route('ambulatory.accept.invitation', ['token' => $this->invitation->token]),
36 | 'role' => $this->invitation->role,
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Mail/ResetPasswordEmail.php:
--------------------------------------------------------------------------------
1 | token = $token;
25 | }
26 |
27 | /**
28 | * Build the message.
29 | *
30 | * @return $this
31 | */
32 | public function build()
33 | {
34 | return $this->subject(config('app.name').': Reset Password Notification')
35 | ->view('ambulatory::emails.forgot-password', [
36 | 'link' => route('ambulatory.password.reset', ['token' => $this->token]),
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/MedicalForm.php:
--------------------------------------------------------------------------------
1 | hasMany(Booking::class, 'medical_form_id');
59 | }
60 |
61 | /**
62 | * The medical forms belongs to a user.
63 | *
64 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
65 | */
66 | public function user()
67 | {
68 | return $this->belongsTo(User::class, 'user_id');
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Migrations/2019_02_13_000000_create_tables.php:
--------------------------------------------------------------------------------
1 | uuid('id')->primary();
18 | $table->string('name');
19 | $table->string('email')->unique();
20 | $table->string('password');
21 | $table->string('avatar')->nullable();
22 | $table->smallInteger('type', false, true)->default(1);
23 | $table->rememberToken();
24 | $table->timestamps();
25 | });
26 |
27 | Schema::create('ambulatory_doctors', function (Blueprint $table) {
28 | $table->uuid('id')->primary();
29 | $table->uuid('user_id')->unique();
30 | $table->string('slug')->unique();
31 | $table->string('full_name');
32 | $table->string('qualification');
33 | $table->date('practicing_from');
34 | $table->text('professional_statement')->nullable();
35 | $table->boolean('is_active')->default(true);
36 | $table->text('working_hours_rule');
37 | $table->timestamps();
38 | });
39 |
40 | Schema::create('ambulatory_specializations', function (Blueprint $table) {
41 | $table->uuid('id')->primary();
42 | $table->string('slug')->unique();
43 | $table->string('name');
44 | $table->string('description')->nullable();
45 | $table->timestamps();
46 | });
47 |
48 | Schema::create('ambulatory_doctors_specializations', function (Blueprint $table) {
49 | $table->uuid('doctor_id');
50 | $table->uuid('specialization_id');
51 | // $table->timestamps();
52 |
53 | $table->unique(['doctor_id', 'specialization_id'], 'doctor_id_specialization_id_unique');
54 | });
55 |
56 | Schema::create('ambulatory_medical_forms', function (Blueprint $table) {
57 | $table->uuid('id')->primary();
58 | $table->uuid('user_id')->index();
59 | $table->string('slug')->unique();
60 | $table->string('form_name')->index();
61 | $table->string('full_name');
62 | $table->date('dob');
63 | $table->string('gender');
64 | $table->text('address');
65 | $table->string('city');
66 | $table->string('state')->nullable();
67 | $table->string('zip_code');
68 | $table->string('home_phone')->nullable();
69 | $table->string('cell_phone');
70 | $table->string('marital_status');
71 | $table->timestamp('verified_at')->nullable();
72 | $table->timestamps();
73 | });
74 |
75 | Schema::create('ambulatory_health_facilities', function (Blueprint $table) {
76 | $table->uuid('id')->primary();
77 | $table->string('slug')->unique();
78 | $table->string('name');
79 | $table->string('address');
80 | $table->string('city');
81 | $table->string('state')->nullable();
82 | $table->string('country');
83 | $table->string('zip_code');
84 | $table->timestamps();
85 | });
86 |
87 | Schema::create('ambulatory_invitations', function (Blueprint $table) {
88 | $table->uuid('id')->primary();
89 | $table->string('email')->unique();
90 | $table->string('role');
91 | $table->string('token')->unique();
92 | $table->timestamps();
93 | });
94 |
95 | Schema::create('ambulatory_schedules', function (Blueprint $table) {
96 | $table->uuid('id')->primary();
97 | $table->uuid('doctor_id');
98 | $table->uuid('health_facility_id');
99 | $table->date('start_date');
100 | $table->date('end_date');
101 | $table->integer('estimated_service_time_in_minutes')->nullable();
102 | $table->timestamps();
103 |
104 | $table->unique(['doctor_id', 'health_facility_id']);
105 | });
106 |
107 | // to store custom availabilities of schedule.
108 | Schema::create('ambulatory_availabilities', function (Blueprint $table) {
109 | $table->uuid('id')->primary();
110 | $table->uuid('schedule_id')->index();
111 | $table->string('type')->default('date');
112 | $table->text('intervals');
113 | $table->date('date');
114 | $table->timestamps();
115 | });
116 |
117 | Schema::create('ambulatory_bookings', function (Blueprint $table) {
118 | $table->uuid('id')->primary();
119 | $table->uuid('schedule_id');
120 | $table->uuid('medical_form_id')->index();
121 | $table->dateTime('preferred_date_time');
122 | $table->dateTime('actual_end_date_time')->nullable();
123 | $table->string('description')->nullable();
124 | $table->boolean('is_active')->default(true);
125 | $table->timestamps();
126 |
127 | $table->unique(['schedule_id', 'preferred_date_time'], 'book_schedule_date_unique');
128 | });
129 | }
130 |
131 | /**
132 | * Reverse the migrations.
133 | *
134 | * @return void
135 | */
136 | public function down()
137 | {
138 | Schema::dropIfExists('ambulatory_users');
139 | Schema::dropIfExists('ambulatory_doctors');
140 | Schema::dropIfExists('ambulatory_specializations');
141 | Schema::dropIfExists('ambulatory_doctors_specializations');
142 | Schema::dropIfExists('ambulatory_medical_forms');
143 | Schema::dropIfExists('ambulatory_health_facilities');
144 | Schema::dropIfExists('ambulatory_invitations');
145 | Schema::dropIfExists('ambulatory_schedules');
146 | Schema::dropIfExists('ambulatory_availabilities');
147 | Schema::dropIfExists('ambulatory_bookings');
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/Policies/AvailabilityPolicy.php:
--------------------------------------------------------------------------------
1 | doctorProfile->id === $availability->schedule->doctor_id;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Policies/DoctorPolicy.php:
--------------------------------------------------------------------------------
1 | id === $doctor->user_id;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Policies/DoctorSchedulePolicy.php:
--------------------------------------------------------------------------------
1 | doctorProfile->id === $schedule->doctor_id;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Policies/MedicalFormPolicy.php:
--------------------------------------------------------------------------------
1 | id === $medicalForm->user_id;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Rules/BookingAvailabilityRule.php:
--------------------------------------------------------------------------------
1 | schedule = $schedule;
25 | }
26 |
27 | /**
28 | * Determine if the validation rule passes.
29 | *
30 | * @param string $attribute
31 | * @param mixed $value
32 | * @return bool
33 | */
34 | public function passes($attribute, $value)
35 | {
36 | return $this->schedule->checkAvailabilitySlot($value);
37 | }
38 |
39 | /**
40 | * Get the validation error message.
41 | *
42 | * @return string
43 | */
44 | public function message()
45 | {
46 | return 'The preferred date time is not available.';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Rules/CurrentPassRule.php:
--------------------------------------------------------------------------------
1 | user()->getAuthPassword());
20 | }
21 |
22 | /**
23 | * Get the validation error message.
24 | *
25 | * @return string
26 | */
27 | public function message()
28 | {
29 | return 'Your current password is incorrect.';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Specialization.php:
--------------------------------------------------------------------------------
1 | belongsToMany(Doctor::class, 'ambulatory_doctors_specializations', 'specialization_id', 'doctor_id');
59 | }
60 |
61 | /**
62 | * boot the model.
63 | *
64 | * @return void
65 | */
66 | protected static function boot()
67 | {
68 | parent::boot();
69 |
70 | static::deleting(function ($item) {
71 | $item->doctors()->detach();
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix');
2 | const webpack = require('webpack');
3 |
4 | /*
5 | |--------------------------------------------------------------------------
6 | | Mix Asset Management
7 | |--------------------------------------------------------------------------
8 | |
9 | | Mix provides a clean, fluent API for defining some Webpack build steps
10 | | for your Laravel application. By default, we are compiling the Sass
11 | | file for your application, as well as bundling up your JS files.
12 | |
13 | */
14 |
15 | mix
16 | .options({
17 | uglify: {
18 | uglifyOptions: {
19 | compress: {
20 | drop_console: true,
21 | }
22 | }
23 | }
24 | })
25 | .webpackConfig({
26 | plugins: [
27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
28 | ],
29 | });
30 |
31 | mix
32 | .setPublicPath('public')
33 | .js('resources/js/app.js', 'public')
34 | .sass('resources/sass/app.scss', 'public')
35 | .version()
36 | .copy('public', '../ambulatorytest/public/vendor/ambulatory');
--------------------------------------------------------------------------------