
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore the step-by-step process of building an admin dashboard for managing hospital appointments. The focus is on creating an intuitive interface that displays various stats and allows admin users to manage appointments effectively.
Next.js
and Tailwind CSS
to style the components and layout.00:00:00 Now let's go from this 404 to a nice looking admin interface that looks something like this.
00:00:08 We want to say welcome admin.
00:00:10 We want to show a database of all of the appointments and the stats of everything that's happening in the hospital at this point in time.
00:00:17 So going back to the file explorer, we can go to app and create a new folder called admin.
00:00:25 And within admin, we can create a new page.tsx where we can run RAFCE.
00:00:31 We can rename it to admin.
00:00:35 and go back to the browser.
00:00:37 And now we can see admin on top left, which means that we are ready to turn this into this.
00:00:44 So let's do that next.
00:00:46 To start working on the admin page, we can first give this div a class name of MXAuto.
00:00:53 That stands for Margin Horizontal.
00:00:56 We're going to make it a flex container and give it a max W of 7xL.
00:01:01 So that's about 1280 pixels.
00:01:04 We can also make it flex call so the elements appear one above another and create some spacing in between the elements of Y14.
00:01:14 Now we can create a header within this div and that header will have a class name equal to admin-header.
00:01:24 And within it, we can create a link.
00:01:28 That link of course will be imported from nextlink and it'll have an href pointing to the homepage with a class name equal to cursor-pointer.
00:01:40 Within here, we can render our logo.
00:01:42 So that's going to be an image coming from next image.
00:01:47 And we can give it a source equal to forward slash assets, forward slash icons, forward slash logo dash full dot SVG with a height of about 32, width of
00:02:00 about 162. alt equal to logo and the class name equal to h-8 and w-fit.
00:02:14 Great.
00:02:14 And also right below that link, we can have a p tag that'll have a class name equal to text-16-semi-bold and it'll simply say admin dashboard.
00:02:27 And that's going to be our header.
00:02:28 So if we go back, you can see we have care pulls and we have admin dashboard.
00:02:33 So now we can start working on the main part of the content.
00:02:37 So going below the header, we can create our main part with a class name equal to admin-main.
00:02:47 And within it, we can have a section.
00:02:51 And that section will have a class name equal to w-full space-y-4.
00:03:00 And we can render an H1 with a class name equal to header.
00:03:06 That's going to say welcome.
00:03:09 And we can also show an emoji right here of a waving hand and a P tag with a class name equal to text-dark-700.
00:03:24 And it'll say, start the day with managing new appointments.
00:03:29 So if we save it and go back, this looks good.
00:03:33 And now we can show those three stats card.
00:03:35 Okay.
00:03:36 That's pretty cool.
00:03:37 So we first have to get that data from the backend.
00:03:40 So what we can do is create a new section below this section that'll have a class name equal to admin-stat.
00:03:50 And here we'll have to render that card.
00:03:54 This card will of course be a reusable component as we'll be using it three different times.
00:04:00 So what we can do is create a new component called statcard.tsxfce.
00:04:10 And we can just import it right here, statcard coming from components.
00:04:16 And now we can choose which props we want to pass to it.
00:04:21 First of all, we'll have a type of the appointment.
00:04:24 So it can be either pending, canceled, or actual real appointments.
00:04:29 So we can say type appointments.
00:04:32 We can then do a count of how many we have.
00:04:35 We can do a label.
00:04:37 In this case, we can say something like scheduled appointments.
00:04:41 And we can also render an icon, which can be something like assets, icons, appointments.svg.
00:04:50 And now we can duplicate this two more times.
00:04:53 The second time, we will discuss the pending appointments.
00:04:58 And the third card will talk about canceled appointments.
00:05:02 We can change this fake count for now.
00:05:04 We will later on modify it, of course, to fetch it from real database.
00:05:08 And we can also change the label.
00:05:10 The second one will be pending.
00:05:12 The third one will be canceled.
00:05:16 And we can also change the icons.
00:05:18 This one will be pending and this one will be canceled.
00:05:23 Great.
00:05:24 So now if we go back, of course, we'll only see three texts that say stat card.
00:05:30 But if we dive into the stat card, we can now accept all of these props and start implementing it.
00:05:36 So which props do we get?
00:05:38 Well, it's going to be the count, which we can default to zero if nothing is passed, a label, an icon, and a type.
00:05:47 And this can be of a type stat card props.
00:05:52 So we can define that interface right here at the top, stat card props with a type equal to appointments or pending or canceled with a count of a type number,
00:06:07 a label of a type string and an icon of a type string as well.
00:06:13 And now we can start creating this div which will act as a card.
00:06:16 So how do we do that?
00:06:18 Well, we give it a class name equal to and we'll make it a bit dynamic.
00:06:24 So we'll use the CLSX coming from CLSX and this allows us to modify the styles of the card depending on dynamic properties.
00:06:34 So always it'll be a stat-card, which will look something like this.
00:06:39 Oh, we cannot see it right now because we don't have the content.
00:06:42 But if we say test, you can see test, test, test.
00:06:45 It has the basic default styles.
00:06:48 But now we can provide the options object.
00:06:53 And then depending on the state, we can apply some different classes.
00:06:56 For example, we can apply bg-appointments if the type is triple equal to appointments.
00:07:02 We can apply bg-pending if the type is equal to pending and bg-cancelled if the type is equal to cancelled.
00:07:09 So if we go back now, you can see how it changes depending on the styles.
00:07:14 Of course, feel free to play with it further.
00:07:16 We can even modify the styles later on.
00:07:18 For now, let's also create an inner div within this div that'll have a class name equal to flex, items-center, and a gap of four.
00:07:31 Within it, we can render a different image for each one.
00:07:35 And how do we know which image to render?
00:07:38 Well, because we already have the source of the icon.
00:07:41 So we can just say icon, give it a height of 32, a width of 32, an alt tag of label, and a class name equal to size-8, w-fit.
00:07:56 So if we do that and go back, you can see three different icons.
00:08:00 Looking good.
00:08:02 We can also create an h2 inside of that div with a class name of h of text-32-bold and text-white.
00:08:14 And we can just render the count of appointments in each category.
00:08:20 And below the div, we can render another p tag with a class name, text-14-regular.
00:08:27 And we can render the label of that appointment card.
00:08:32 So now if we go back, you can see schedule appointments, pending appointments, and canceled appointments as well.
00:08:38 And if you want to search and modify these styles, just search for it.
00:08:42 Cancelled.
00:08:43 And you'll find it in the Tailwind Config right here, where we have different backgrounds coming from assets, images, and then we have different PNG backgrounds
00:08:53 as well.
00:08:53 And these are basically just different gradients of colors.
00:08:56 I think you can see it here.
00:08:57 It's a bit bluish.
00:08:58 Here it's a bit reddish and so on, but it looks like it's not fetching them properly right here.
00:09:04 Let's see.
00:09:05 Let me try to search for assets, images, and then try to find these different backgrounds.
00:09:11 If I go to public, assets, images.
00:09:15 And we do have appointments, BG, PNG right here.
00:09:19 Oh yeah, as you can see, it is just the basic gradient and then it changes the color depending on the state.
00:09:26 So I do believe that this is good.
00:09:28 Oh, but I just misspelled it here.
00:09:30 Appointments.
00:09:32 So if I do it, okay, that's a bit better.
00:09:34 Yellow, blue, and then red.
00:09:36 Perfect.
00:09:37 And we have our three cards.
00:09:38 But of course, we want to make this data real.
00:09:41 We want it to actually reflect the appointments that we have in our database.
00:09:46 So let's go back to the code and go back to the page where we have our admin dashboard.
00:09:52 And we need to try to fetch all of the recent appointments.
00:09:56 So let's head over to appointmentactions.ts And let's create a new export const get recent appointments list.
00:10:11 That's going to be an async function with a try and catch block.
00:10:16 In the catch, we can simply console.log the error.
00:10:19 And in the try, we can fetch all the appointments by saying const appointments.
00:10:26 is equal to await databases.listDocuments, and we want to get the documents from the database with the database ID, the one that we have stored.
00:10:39 We want to fetch from the appointment collection, so we need to pass its ID.
00:10:45 And we want to query by...
00:10:47 So query.order descending by the field created at.
00:10:56 And we need to import this query from Node AppWrite.
00:10:59 So we're showing the newest ones at the top.
00:11:01 Now, we also want to return the count of different appointments.
00:11:06 Are they pending?
00:11:06 Are they canceled?
00:11:08 Or something else?
00:11:09 So we can say const initial counts is equal to an object where we have scheduled count set to zero by default.
00:11:20 We have pending count and we also have a canceled count as well.
00:11:26 And now we want to iterate over the counts and increase the count for every appointment that is of that specific state.
00:11:35 You can see ChatGPT even recommends this to me, but let's go ahead and write it together.
00:11:40 I'm going to say const counts is equal to, I'll wrap it in parentheses for TypeScript and say appointments.documents as appointment array and we have to
00:11:54 import this appointment from types.Reduce.
00:11:58 So we'll run a reduce function on it and reduce typically takes in the accumulator and then the value.
00:12:05 So that looks something like this.
00:12:08 We can then close it, and it also takes the initial counts as the second parameter.
00:12:14 And then in the middle of the function, we have to define the logic of when we will update each one of these.
00:12:20 And I like what Copilot suggests here.
00:12:23 If the appointment data status is scheduled, we'll just increase the accumulator, which is the default state, by one.
00:12:32 If the status is pending, we'll increase the pending count by one.
00:12:36 And if the status is canceled, we'll increase the canceled count by one.
00:12:41 So feel free to pause the video here and type this out alongside me as well.
00:12:46 But once we have all of those values, we can form it into a data object.
00:12:51 So cons data.
00:12:53 is equal to, we want to pass over the total count.
00:12:58 So that's going to be appointments.total.
00:13:01 And that's given to us by AppRight immediately.
00:13:04 We want to spread all of the counts that'll include the pending, canceled, and so on.
00:13:10 And finally, we want to return all the documents, which is going to be equal to appointments.documents.
00:13:16 These are the actual appointments.
00:13:18 Finally, we can return parse stringify the data.
00:13:23 Great.
00:13:24 So now if we go back, we can call this server action and get access to all the appointments by saying const appointments is equal to await get recent appointments list.
00:13:40 and call it as a function and import it from lib appointment actions and make the admin page async since we're using a top level await.
00:13:51 Now, with this done, we can just use this appointments variable and pass a real value for the count by saying appointments.scheduled count We can repeat
00:14:04 this over here, appointments.pendingCount.
00:14:08 And also right here, appointments.canceledCount.
00:14:13 So if we go back, you can see zero, two, and then zero.
00:14:17 And this is exactly what we have done.
00:14:19 I have created two pending appointments.
00:14:22 That's perfect.
00:14:23 The doctors weren't yet able to schedule them, so that's totally okay.
00:14:27 This works great.
00:14:28 But now we want to focus on the biggest part of the admin dashboard, which is the data table where we show all of these appointments.
00:14:37 So let's go back.
00:14:38 And let's render a data table right here below the last section.
00:14:44 This data table will be a custom component.
00:14:47 So let's create it in components, datatable.tsx and run RAFCE.
00:14:55 So if we go back, we can now simply import the data table.
00:15:00 And to it, we need to pass the data equal to appointments.documents.
00:15:05 So we're basically passing all the appointments.
00:15:08 And we want to pass a list of columns that we want the table to have.
00:15:12 So we can say columns is equal to, and this will actually be a new component which we can create.
00:15:19 So let's actually create a new folder within the components folder called table.
00:15:24 Within the table, we'll create a columns.tsx part where we can run RAFCE.
00:15:33 And it looks like I misspelled it.
00:15:34 So I'm going to say columns and fix it right here.
00:15:40 Okay.
00:15:40 That's good.
00:15:41 So we have columns and actually this will be lower cased as it will not necessarily be a component.
00:15:47 It's just going to be an array of different columns.
00:15:50 And let's also move the actual data table into the table folder.
00:15:54 So it's easier to understand what this is related to.
00:15:58 This will fix the import.
00:16:00 So we have to import it once again.
00:16:02 And we also have to import the columns from table columns.
00:16:08 Great.
00:16:09 Now, of course, nothing is happening yet because this data table is just an empty component.
00:16:14 But if we go back and go here, you can see that we have a data table ready to get developed.
00:16:22 So let's do that next.
00:16:24 To start working on our table, we'll use a ShadCN data table, which is a powerful table and data grid built using 10-stack table.
00:16:35 As you can see here, it is completely responsive.
00:16:38 You can have different filtering and it also works great on mobile devices.
00:16:42 You can select different things.
00:16:44 You can sort, filter.
00:16:46 You can do all sorts of different things.
00:16:48 And of course there's pagination as well.
00:16:50 So let's see how we can use it.
00:16:53 Every data table or data grid I've created has been unique.
00:16:56 They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
00:17:03 It doesn't make sense to combine all of these variations in a single component because we'll lose flexibility.
00:17:09 So instead of a data table component, we can provide a guide on how to create your own.
00:17:14 We'll use the basic table, extend it, and then create something customized.
00:17:19 So let's first install a Schatzian data table by running mpx schatzianui add table.
00:17:27 And we also need to add a 10 stack react table as a dependency.
00:17:32 So let's do that as well.
00:17:35 And we can then check out the prerequisites here.
00:17:37 You can see different pieces of data for a table and then also a structure.
00:17:41 This is the same structure that we have.
00:17:43 We have a table, columns, and then we have a data table.
00:17:47 So let's see how we can create a basic table.
00:17:51 This is still within a columns page.
00:17:53 So let's go ahead and copy this entire file and paste it into the columns part of our table.
00:18:00 That's going to be columns and let's basically override everything.
00:18:06 That's good.
00:18:07 And since we overrode it, we'll also have to modify the import because now it's no longer a default import.
00:18:13 It is a named import.
00:18:14 So we'll just modify that and let's continue.
00:18:19 We'll try to make it work with a basic example.
00:18:22 Next, we have a data table.
00:18:24 So let's copy this entire part and we'll paste it over into our data table.
00:18:30 That's good.
00:18:31 No errors.
00:18:32 Looking good.
00:18:34 Next, we need to render our table by getting some data and then just calling it.
00:18:39 We are already doing that.
00:18:41 The only thing that we don't yet have is the actual data.
00:18:44 So let's just import this part to get us fake data.
00:18:48 Oh, looks like data table will also have to be a named import.
00:18:51 So let's fix that.
00:18:53 And right at the top, let's create this function.
00:18:56 Let's import the payment from table columns and let's get that data and then pass it over to the table.
00:19:03 So what we're doing for now is just trying to create a mock table just so we can see how it looks like.
00:19:10 So instead of passing appointment documents, I'll comment that out and I'll just pass this fake data over here.
00:19:18 Okay, and then later on, they explain how we can modify the columns.
00:19:22 But for now, let's just see how it looks like.
00:19:24 So if we go here, we already have a basic table.
00:19:28 We have a status of pending, an email of mediaexample.com, and the amount.
00:19:34 This is pretty good right off the bat, but definitely not looking like this just yet.
00:19:39 So let's see how we can modify our columns to look a bit better.
00:19:42 They provided us a header and a cell to expand the look and feel of our column.
00:19:50 So let's go back into code, into columns, and we'll here modify this amount right here with a new header and with a formatting.
00:20:02 So if we go back, you can see that this is an actual dollar amount.
00:20:05 We have row actions.
00:20:07 So let's see how we can add that.
00:20:09 We'll copy all the actions.
00:20:12 And we just need to add them as one of the columns.
00:20:16 So let's go back and let's add them right here at the top.
00:20:22 And of course we'll have to import all of these.
00:20:26 And of course for that, we'll have to install the dropdown menu.
00:20:29 So let's do just that by running ChatCN UI latest add dropdown menu, and then we can get all of these imports from the top.
00:20:42 Once again, we're just following the documentation for now.
00:20:46 If we now go back, you can see that each row now has actions that we can check out.
00:20:52 What else do we have?
00:20:54 Well, of course the pagination.
00:20:56 So we can update our data table by importing the get pagination row model.
00:21:02 And then we just add it to the table.
00:21:05 This will automatically paginate your rows into pages of 10. So let's do that.
00:21:10 I'll import get pagination row and add it to use react table under our data table.
00:21:16 So that's going to be right here.
00:21:19 Get pagination row model.
00:21:21 And we'll import it from 10-stack React table.
00:21:24 And if we go back, there is nothing yet because we only have one transaction.
00:21:28 But if I go to page and I add more transactions here, I'll basically duplicate this many, many times below.
00:21:36 So we surpass 10. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. Oh, I was exactly at 10. So let me duplicate it once more.
00:21:46 And it's not yet paginating.
00:21:48 And that's because we have to add pagination controls, such as a button right here.
00:21:53 So let's add this div with pagination controls.
00:21:57 And we can add that right here below this data table.
00:22:00 And we have to import the button.
00:22:02 If we do that and go back, you can now see previous and next, which means that the pagination is fully working.
00:22:11 Let's see what else we have.
00:22:12 Then we have sorting and all of these additional functionalities.
00:22:16 As you can see, they're pretty simple to add.
00:22:18 You just import some things, you add it to use React table, and then you add the actions to specific columns so you can filter it,
00:22:25 sort it out, and do more stuff.
00:22:28 It's completely extendable and customizable so you can do whatever you like.
00:22:34 But now let's modify the columns so they actually work for our example and include the data about appointments that we want to show,
00:22:43 not transactions.
00:22:44 So let's move those actions all the way to the bottom by selecting them and then moving them below a few of these things.
00:22:53 All the way here.
00:22:54 There we go.
00:22:55 That's good.
00:22:56 So now we have these different columns that we can then modify.
00:22:59 As you can see, we have a status, we have email and so on.
00:23:03 I'll show you how to add all of the other ones later on.
00:23:08 But for now, let's modify the data so we don't show the data about transactions or payments.
00:23:14 We actually show the data from our appointments.
00:23:18 We can do that by deleting this getData function and this getter.
00:23:22 And right here below, we can actually just call the data table by passing the appointments.documents.
00:23:31 If we do that, you'll see two different appointments.
00:23:35 Although the data is not there, now we will fill it out with the correct columns.
00:23:40 So let's head over to columns and let's create a new one above the status.
00:23:45 This one will have a header equal to ID and it'll also render a cell.
00:23:51 We have to add a comma here.
00:23:53 The cell.
00:23:55 is typically a function that extracts data about the row and then just returns something.
00:24:01 In this case, we're going to return a p tag with a class name equal to text-14-medium.
00:24:10 And here we can render the row.index plus one, because we want to start from one and not zero.
00:24:17 So if we do this and go back, you can see that now we have one and two.
00:24:22 These are our appointments.
00:24:24 After the appointment ID, we want to add the patient.
00:24:27 So let me show you how we can do that.
00:24:30 Again, just open up a new object, which has something to do with the column.
00:24:34 And now we'll add an accessor key.
00:24:38 Okay, this is interesting.
00:24:40 So the accessor key, in this case, will be patient.
00:24:44 The header will be patient, and then we'll have a cell that presents the data about that specific column.
00:24:51 So we do the same thing as before where we just extract the value from a row.
00:24:58 And typically how it works is you can get the real data.
00:25:02 In this case, it'll be an appointment.
00:25:05 by getting it from the row.original.
00:25:08 But how does this row.original know what we want to get?
00:25:11 Is it an appointment or is it a patient or something else?
00:25:14 Well, we do that based on the accessor key.
00:25:18 So here, the accessor key is patient.
00:25:20 So we get the patient and then from that patient, we can get the appointment name.
00:25:24 Let me show you how you would do that.
00:25:25 You can just say return ap tag that has a class name equal to text-14-medium.
00:25:35 And here you can return the appointment.patient.name and save.
00:25:42 If you do that and fix this issue right here, go back, you can now see Adrian JS Mastery, which is the name of the patient that registered for that appointment.
00:25:52 Now there's also a shorter way to write this.
00:25:55 You don't have to extract this into an additional variable.
00:25:58 Rather, you can just say row.original.patient.name right here, remove this line, and then have an immediate return.
00:26:06 So instead of a curly brace, you can just immediately return this p tag in a single line.
00:26:12 And this will work exactly the same.
00:26:15 So now we have the ID and the patient.
00:26:17 Now let's move over to the status of the appointment.
00:26:20 So for the status, we have the accessor key of status and the header.
00:26:24 So let's modify how the cell will look like.
00:26:27 We get the row and we can automatically return a div with a class name equal to.
00:26:35 min-w-115px and within it, we can return a status badge.
00:26:42 And this status badge will be a new component which we can create.
00:26:46 So let's create it by going to components and creating a new status.
00:26:52 badge.tsx component, where we can run RAFCE.
00:26:58 And we can now import it right within here, status badge.
00:27:03 And to it, we only have to pass one thing, which is the status.
00:27:08 So status will be equal to row.original.status.
00:27:14 And we can just dive right into it and start implementing it.
00:27:18 So we immediately know that we have to accept the status as the first and only prop and it'll be of a type.
00:27:25 Status is status coming from types.
00:27:30 We can return a div that has a class name equal to.
00:27:34 It's going to be a bit of a more complex class name where we'll use a CLSX that allows us to provide a basic styles like status badge for all of them,
00:27:46 but then we can specify which classes we want to provide depending on the status.
00:27:52 So we can say apply a BG green of 600 if the status is scheduled.
00:27:59 So let's do scheduled right here.
00:28:01 Then do something like BG-blue-600 if the status is pending.
00:28:09 do bgred600 if the status is canceled.
00:28:13 Right within it, we can also render an image to show that status.
00:28:18 So let's import the image from next image and give it a source that will change depending on the status.
00:28:24 So we can get the status icon from constants and then pass to it the status.
00:28:30 If you take a quick look, you'll see that depending on the state, it'll render a different SVG.
00:28:36 Let's also give it an alt tag equal to status.
00:28:41 Let's give it a width of about 24, a height of 24, and a class name of h-fit and w-3.
00:28:51 So if we save it and go back, you can now see this great looking batch.
00:28:57 Cool.
00:28:58 Let's also add the p tag.
00:29:01 So this p tag will simply render the status.
00:29:06 But we can apply different styles depending on which status we have.
00:29:09 So let's give it a class name equal to CLSX.
00:29:14 It'll always have a text 12, semi-bold, and capitalize.
00:29:19 But depending on the status, it'll change colors.
00:29:22 So it'll be text-green-500 if the status is scheduled.
00:29:29 It'll be something like...
00:29:31 text-blue-500 if the status is pending and text-red-500 if the status is canceled.
00:29:40 So now if we save that and go back, you can see we have two pending statuses, which is exactly what's happening with our existing appointments.
00:29:50 Great.
00:29:51 Moving back, we can go to the next column and that's going to be the actual appointment date and time.
00:29:58 So let's just see what we have here.
00:30:01 Yeah, instead of email, we can now do schedule and the header will say appointment and we can render the cell.
00:30:09 It's going to be a function with an immediate return where we'll return a p tag.
00:30:14 So let's do just that.
00:30:16 P tag with a class name of text dash regular or no text dash 14 dash regular.
00:30:23 with a min dash w of 100 pixels.
00:30:27 And here we can render the format date time from libutils to which we can pass the row.original.
00:30:35 dot schedule, and outside of that, we can call the dot date time.
00:30:41 So this will properly format this schedule and it should look something like this.
00:30:46 Perfect.
00:30:47 Looking better and better every second, but of course we'll have to modify the overall table styles very soon.
00:30:54 For now, we're just working on the columns.
00:30:57 Now let's also work on the doctor.
00:31:00 So instead of the amount, let's actually call this primary physician.
00:31:05 Let's properly spell it.
00:31:07 The header can simply say doctor.
00:31:10 And in the cell, we can try to find the doctor belonging to this appointment by saying const doctor is equal to doctors coming from constants.find And
00:31:22 we want to find a doc where the doc.name is triple equal to row.original.primaryPhysician.
00:31:31 Once we have it, we want to return a div where a class name is equal to flex.
00:31:40 items-center and a gap of three.
00:31:43 And we can render an image where the source is equal to doctor?image.
00:31:50 We can give it an alt tag equal to doctor.name.
00:31:55 Let's also do a width of about a hundred.
00:31:59 We can do a height of about a hundred and a class name of size-8.
00:32:05 And of course, what would that doctor be without a name?
00:32:08 So right below the image, we can render a p tag, give it a class name equal to whitespace no wrap.
00:32:16 And there we can say drdoctor and then render the doctor.name.
00:32:21 Let's not forget to import the image from next image.
00:32:25 If we do that and go back, you can now see Dr.
00:32:29 Evan Peter and Leila Cameron.
00:32:32 That is great.
00:32:34 What other information do we need to show?
00:32:37 Well, I think that's more or less it.
00:32:39 Now we just want to show the actions that doctors can do on those specific appointments.
00:32:45 So let's clear out the entire action cell right here.
00:32:50 Let's give these actions a header.
00:32:53 That's going to be a div with a class name equal to padding left of four to divide it a bit from the left.
00:33:01 And it's going to say actions.
00:33:03 Now within here, we can return a div and this div will have a class name equal to flex and the gap of one.
00:33:12 And within here, we want to render an appointment.
00:33:17 So this is a new component which we need to create.
00:33:20 So let's do that right away by creating a new component called appointmentModel.tsx.
00:33:26 And within here, we can run RAFCE.
00:33:31 And now we can call it as a component.
00:33:33 So that's going to be appointmentModel.tsx.
00:33:37 There we go.
00:33:38 And we can import it from appointment model.
00:33:42 If we do that and go back, you can see that now we have appointment model as the action.
00:33:47 But of course, what we want to actually do is this.
00:33:50 We want to have two actions that'll open up different models that'll say schedule and cancel.
00:33:55 And then that'll open this type of the schedule appointment model.
00:34:00 And for that, we'll use a ChatCN dialogue component.
00:34:04 Before we use the alert dialogue, now we'll just use a regular dialogue.
00:34:09 So let's just install it by adding MPX, Shatzen UI latest, add dialogue.
00:34:17 After we add it, we need to copy its imports right at the top.
00:34:23 No more dropdown.
00:34:23 So we can delete that and we can copy its usage.
00:34:32 Oh, but this will not be in the columns, rather it'll be in the appointment model.
00:34:36 So I can paste it right here and indent it.
00:34:41 And the same things goes for the imports.
00:34:44 It's going to be within the appointment model.
00:34:49 There we go.
00:34:50 So now if we save it and go back, you can see open and I click it and there it is.
00:34:57 Great.
00:34:58 Now we have to modify it.
00:34:59 So let's define a new use state.
00:35:04 That's going to be open and set open at the start equal to false.
00:35:09 We have to import use state from react.
00:35:12 And we also have to define this as a use client component.
00:35:18 To this dialog, we can pass the open state equal to open.
00:35:23 And let's properly spell this with a lowercase o.
00:35:27 And let's give it a onOpenChange, which will be equal to setOpen.
00:35:32 So this is the setter function.
00:35:34 The dialog trigger will say different things.
00:35:37 So it has to either say schedule or cancel.
00:35:41 So within the dialog trigger, we can render a button.
00:35:46 which we have to import from components button, and we can give it the as child prop, which means that the button acts as the trigger.
00:35:55 The variant of the button will be equal to ghost, and we can give it a class name.
00:36:01 Equal to, it'll be dynamic.
00:36:03 capitalized, and now we have to choose whether it's a cancel or a schedule button.
00:36:09 So for that reason, we can pass the type into this appointment model.
00:36:14 So we can say type is equal to, and here we can say schedule.
00:36:19 Okay.
00:36:22 Back in the appointment model, we can accept that type by saying type, and that'll be of a type, either schedule or it'll be cancel.
00:36:33 So now depending on that type, we can show different things in different colors.
00:36:38 So let's say if type is triple equal to schedule.
00:36:43 Then give it a text dash green dash 500 color and within it, render the type text.
00:36:52 So now if I go back, you can see only the schedule button.
00:36:57 And if I go back here and I add another appointment model, they'll say something like cancel.
00:37:03 You should be able to see it here as well.
00:37:06 Great.
00:37:08 So let's move down to the dialogue content.
00:37:11 Let's give it a class name equal to shad-dialogue on small devices, max-w-md.
00:37:19 So this is a max width of 450 pixels.
00:37:23 And let's open it.
00:37:24 There we go.
00:37:25 Looking better.
00:37:26 And let's also style the dialogue header by giving it a class name of margin-bottom of four, space-y of three.
00:37:36 And DialogTitle will have a class name of capitalize and it'll render the type of the appointment.
00:37:49 Okay.
00:37:50 So that'll look something like this, schedule appointment, or if we click cancel, cancel appointment.
00:37:56 In the description, we can say something like, please fill in the following details to type.
00:38:05 That's either cancel or schedule an appointment.
00:38:10 So one more time, let's check it out.
00:38:12 Yep.
00:38:13 This is looking great.
00:38:15 We want to go below the dialogue header, still within the dialogue content.
00:38:19 And now within here, we can render an appointment form.
00:38:24 We have created this form before.
00:38:26 but we still have to pass a lot of information to it, such as who is the user that's canceling it?
00:38:31 Who is the patient?
00:38:32 Do we want to cancel or schedule it?
00:38:34 What do we want to do with it?
00:38:35 And so much more.
00:38:36 So let's first get that info into the appointment model through props from columns.
00:38:42 So right here, we'll modify this first appointment model and pass all of these necessary things, such as the patient ID equal to row.original And in this case,
00:38:58 it might make more sense to just destructure the original as well, just so we don't have to repeat ourselves.
00:39:05 And we can rename it to data, like this.
00:39:09 So now we can just say data.patient.dollarsideID.
00:39:14 Let's also add a user ID, which will be equal to data dot user ID.
00:39:18 We'll also have the actual appointment.
00:39:21 So this will be equal to just data because data is the appointment.
00:39:25 And we'll have a title equal to schedule appointment.
00:39:29 And we'll also have a description.
00:39:32 So that's going to be something like description, please confirm the following details to schedule.
00:39:42 And now we can duplicate this right below.
00:39:46 We have to change the type from schedule to cancel.
00:39:51 The title will say cancel appointment and we'll say, are you sure you want to cancel your appointment?
00:39:58 So let's do that here.
00:39:59 Are you sure you want to cancel your appointment or you want to cancel this appointment?
00:40:07 Let's do it like that.
00:40:09 Okay, great.
00:40:11 Now let's move into the appointment model and accept all of these props.
00:40:16 We're getting the type, we can get the patient ID, the user ID, and we can also get the appointment itself.
00:40:24 Let's define the types for that.
00:40:26 Oh, here I defined the types.
00:40:29 Above, I actually get them.
00:40:30 So let's get them here.
00:40:32 Type, patient ID, user ID and the appointment.
00:40:35 And now we can define the types.
00:40:38 It's mostly going to be just strings.
00:40:39 So patient ID is of a type string.
00:40:42 User ID is of a type string.
00:40:44 The appointment is of a type appointment coming from upright types, and it's going to be optional.
00:40:50 And in this case, I don't even believe we'll need a title and a description.
00:40:54 So for now, I will just remove them from here and here.
00:40:59 Okay, great.
00:41:00 So now we can pass all the necessary things into the appointment form.
00:41:04 Let's just see how this looks like right now.
00:41:06 Okay, already this is looking good.
00:41:09 So let's pass all the necessary details.
00:41:12 That's going to be the user ID equal to user ID.
00:41:17 We need to pass the patient ID equal to patient ID.
00:41:21 We need to pass the type equal to type, the appointment.
00:41:25 So we know what to cancel and we can pass the set open state of the model to set open.
00:41:31 Now let's go into the appointment form and ensure that we're properly accepting all of those fields.
00:41:36 It looks like we're not.
00:41:37 We're just getting the user ID, patient ID, and the type.
00:41:41 We also need to get the appointment and the set open.
00:41:46 So let's define their types.
00:41:49 Appointment will be of a type appointment coming from AppWrite types.
00:41:54 And the setOpen will just be a function that returns void.
00:41:59 Great.
00:42:00 Now let's put these to use.
00:42:02 We don't need these cons and logs when we were debugging before.
00:42:05 Before we only implemented a create appointment.
00:42:09 But now we can also ensure that it works for different types.
00:42:13 So let's see, here we have if create, and now we want to add an else to this if.
00:42:21 So if we're not creating, we must be canceling.
00:42:24 So we can say else const appointment to cancel.
00:42:29 And here we want to form all the details about that appointment.
00:42:33 So we're going to get the user ID.
00:42:36 We're going to get the appointment ID, which is equal to appointment?$id and want to get all the appointment information.
00:42:46 So that's going to be appointment and that's going to be an object of primary physician.
00:42:52 is equal to values.primaryPosition because it's coming from the form that we're using to update it.
00:42:58 So like here, we want to get a new value.
00:43:02 Next, we also have a schedule.
00:43:05 So we can say newDate values.schedule.
00:43:09 We also have a status.
00:43:10 So it can be a new status, which can be of status as status and also cancellation reason equal to values.cancelationReason.
00:43:21 And outside of the appointment, we're going to provide the type.
00:43:24 Are we canceling it?
00:43:26 Are we updating it?
00:43:27 Are we doing something else?
00:43:28 So yeah, this will actually be not appointment to cancel.
00:43:31 It's going to be appointment to update because we can only update the status as well.
00:43:36 It doesn't mean we're canceling it.
00:43:38 Now, still within this else.
00:43:42 We can say const updated appointment is equal to await update appointment to which we pass this data.
00:43:49 And you can see that this is yet another server action that we can call.
00:43:53 So let's navigate over to appointment.actions.ts and let's create a new one by saying export const update appointment.
00:44:06 It's equal to an async function that accepts a couple of things inside of an object.
00:44:12 So we'll destructure it.
00:44:14 Appointment ID, user ID, appointment itself, and then the type of the update we're trying to achieve.
00:44:23 And all of that is equal to the updateAppointmentPerRamps.
00:44:27 So updateAppointmentPerRamps.
00:44:31 But let's properly end this function right here.
00:44:34 So that's supposed to be a function like this.
00:44:37 Great.
00:44:37 So what do we want to do here?
00:44:39 We can open up a try and catch block as always to take care of the errors.
00:44:44 And we can do a typical console.log and say an error has happened.
00:44:50 But if everything goes right, we want to update the appointment to schedule.
00:44:55 So we can say const updatedAppointment is equal to await databases.updateDocument.
00:45:06 We need to pass the database in which the document is contained.
00:45:10 So that is the database ID.
00:45:13 Next, we want to pass the collection ID where the appointment is.
00:45:17 That is the appointment collection.
00:45:19 Then we want to pass the ID of the document we want to update.
00:45:23 And finally, we want to pass the actual data for the appointment that we want to update contained right here.
00:45:30 Once we have all of that, we should have our updated appointment.
00:45:34 So let's do a quick if check and check if there is no updated appointment.
00:45:39 We can throw a new error.
00:45:41 Else we want to send over a successful SMS confirmation.
00:45:46 So I'm going to say SMS notification, but for now I'll leave that just as a comment.
00:45:51 I'll revalidate the path.
00:45:54 That means we want to actually update the admin route so the changes are reflected.
00:46:00 And I will just return parse.stringify or parse stringify the updated appointment.
00:46:08 And this SMS will be a to-do for later on.
00:46:12 So now that we have this appointment action for the update, we can go back and we can import it right here and check this data we're passing over.
00:46:21 It seems that the property appointment ID is incompatible.
00:46:24 Okay.
00:46:26 Oh yeah, that's because it's not sure whether it will be there or not.
00:46:31 So we can add the exclamation mark at the end because we know it will be there.
00:46:36 And we can finally check if we get back the updated appointment.
00:46:41 We can set open to false only if set open exists, just so we don't break the application.
00:46:49 And we can run a form.reset as we have successfully reset this thing.
00:46:55 And I think we're missing one last closing brace.
00:46:59 So with this, our form should now be able to update as well as cancel different appointments.
00:47:07 Let's close it and let's check it out by going right here.
00:47:11 I will zoom it out all the way up and click schedule.
00:47:15 There we go.
00:47:16 What a wonderful model.
00:47:18 Let's compare it with the one in the design.
00:47:21 Okay, it just says schedule appointment.
00:47:23 So we don't necessarily need this new appointment thing.
00:47:27 This should definitely be conditionally rendered.
00:47:29 So let's go back to the appointment form and the appointment model right here.
00:47:36 And let's find this new appointment.
00:47:39 There we go.
00:47:40 This is the section we're talking about.
00:47:42 So this will be there only if the type is triple equal to create.
00:47:47 Okay.
00:47:48 else we won't show this section.
00:47:50 So now if I save this and go back, there we go.
00:47:54 This is much better.
00:47:56 So now doctors can actually see this and we can choose a doctor which we want to assign to this appointment date.
00:48:03 We can leave it the same so we don't necessarily have to change it.
00:48:07 Or wait, not really.
00:48:08 It's taking a new current time, which is not what we want.
00:48:12 If the appointment has been scheduled at a specific date and time before, we want to pre-populate that value.
00:48:19 So let's go back for a second into the appointment form and let's go to the default values.
00:48:25 For all of these, we want to check whether the default values have already been there.
00:48:30 And then we can update them only if we decide to do that, but we still want to know what the default values were.
00:48:36 So we can say if appointment exists, then here we can render the appointment primary physician.
00:48:44 That'll look something like this.
00:48:46 On the schedule, we can check whether an appointment exists.
00:48:50 If it does, we'll return a new date of appointment.schedule, else we'll return a new date.
00:48:57 When it comes to the reason, we'll do a similar thing.
00:49:01 If an appointment exists, then that must mean that there's also an appointment reason.
00:49:07 So let's fix this right here.
00:49:10 Else it'll be an empty string.
00:49:13 And for this physician as well, we can also do an empty string.
00:49:17 So let's actually use a ternary here.
00:49:20 There we go.
00:49:21 This is better.
00:49:22 If appointment exists, then we set it to primary physician.
00:49:26 Else we set it to an empty string.
00:49:28 We'll do a similar thing for the node.
00:49:30 We can do something like appointment node or this.
00:49:34 And finally, we can do the same thing for the cancellation reason.
00:49:38 So if an appointment exists, a cancellation reason might exist as well.
00:49:43 Or an alternative way to write this would be just appointment cancellation reason or an empty string.
00:49:50 I think we're good with the default values right now.
00:49:52 So if I go back to the browser and click schedule, oh, the cancellation reason actually breaks.
00:49:59 So let me bring it back to what we had.
00:50:03 If appointment exists, then do the cancellation reason.
00:50:07 So if I click schedule, you can see that the default values didn't really work.
00:50:11 The doctor is not selected and the appointment time is still the current time.
00:50:16 So let's look into that one more time.
00:50:19 Let's try to console log the actual appointment by saying console.log appointment right here.
00:50:27 Okay.
00:50:28 And going back to the browser, I will open up a console.
00:50:33 Let me check whether that is on the client side.
00:50:36 Yep, it is.
00:50:36 It's on the client side.
00:50:38 So when I click right here on schedule, oh, look at that.
00:50:44 We get undefined for the appointment.
00:50:46 That's the reason why it's not filling it up.
00:50:48 So let's see.
00:50:50 It's coming through props.
00:50:52 But let's see if we're actually passing it into the appointment form once we call it from the appointment model.
00:50:59 Oh, did I misspell it?
00:51:01 No, I don't think I did.
00:51:03 So let's see why this appointment is not coming properly from the appointment model.
00:51:08 Let's see if we're passing it properly into the appointment model itself.
00:51:14 We're calling it here.
00:51:15 Oh, it looks like I said appointment ID instead of appointment.
00:51:20 My bad right there.
00:51:21 I'm going to fix it.
00:51:23 And if I do that and click schedule, you can see that now it actually fills out all the existing information.
00:51:31 Evan Peters, 9.30 on the 28th.
00:51:34 My leg hurts.
00:51:35 I've been running a lot.
00:51:37 And now we can actually change this.
00:51:39 If a doctor, for example, can make it maybe 30 minutes earlier, we can modify that and click schedule appointment or even change the doctor if somebody
00:51:49 else is responsible for the leg hurting.
00:51:51 So let's try to click schedule appointment.
00:51:54 And even though I'm clicking it, nothing is happening.
00:51:57 And I'm not getting an error on the server side.
00:52:00 So let's see where this button is.
00:52:01 Schedule, appointment.
00:52:04 It is a label for the appointment form, which calls this onSubmit function right here.
00:52:10 So it looks like it's not getting into the cells or at least this is not working.
00:52:15 So let's do a console log and say, updating appointment.
00:52:22 And we should get this on the client side.
00:52:25 So if I do this, open up the inspect element, reload the page and click schedule.
00:52:32 I'll try to click schedule again.
00:52:36 And yep, nothing is really happening.
00:52:38 So it looks like we're not getting into this if statement.
00:52:41 Let's see why that is.
00:52:44 We're not getting any errors as well.
00:52:46 So maybe the type is still create for some reason.
00:52:50 Let's console log the type right here before to see what is actually happening.
00:52:56 If I do that and click again.
00:52:59 I don't really see the type either, so is it possible that we don't even go into the onSubmit function?
00:53:06 Well, let's see.
00:53:07 The actual button is here, and it is a submit button, which means that the only job it has is to submit the form.
00:53:16 Since it's a submit button and it's contained within the form, it will do whatever the onSubmit of that form does, which is in this case to call the onSubmit function.
00:53:28 And that function is this one right here.
00:53:31 So let's add a console log right here at the top and say, I am submitting.
00:53:37 Okay.
00:53:38 And we can also console log the type that we're trying to submit right here.
00:53:42 If I do this and just reload the page, click schedule and schedule appointment.
00:53:51 Once again, we're not getting these cons logs at all.
00:53:55 Let's quickly look into this appointment that we're passing as the data right here into the appointment model.
00:54:01 That could be the issue.
00:54:02 So if we go here, it says that the appointment is of a type appointment.
00:54:07 That is good.
00:54:09 No issues there.
00:54:10 But here it is complaining a bit, saying that the appointment has something to do with the payment.
00:54:16 What kind of payment are we talking about here?
00:54:18 We already said that this is a free hospital.
00:54:20 It's possible that it's still looking into the payment from the demo example of the data table that we have used.
00:54:27 So let me search for payment.
00:54:29 And it looks like the payment is imported here, but not being used.
00:54:34 And it's also being used.
00:54:37 Here, oh yeah, column definition is for the payment, but we're not really working with the payment.
00:54:43 We're working with appointments.
00:54:45 So let's remove this type and let's change this payment to appointment.
00:54:53 And even though this needed to be done, this will not be a fix.
00:54:56 This was just a type.
00:54:57 And now, as you can see, we know that this data is not complaining, which at least helps us a tiny bit.
00:55:04 Let's give it a shot with a cancel.
00:55:05 So if we go here, press cancel and enter the cancellation reason.
00:55:10 Let's say just testing.
00:55:13 And click cancel.
00:55:15 You can see that cancel actually does trigger the submit with the type of cancel.
00:55:21 And it seems like it updates the appointment, but we get an error back on the server side saying that the attribute status has an invalid format.
00:55:31 It must be either scheduled, pending or canceled.
00:55:34 So if I check the submit function right here, we're setting the status as status.
00:55:42 And it can either be pending, scheduled, or canceled.
00:55:46 Here I have two Ls.
00:55:48 This means that back in the AppRy dashboard, I have to modify it.
00:55:53 So let's head over to the database, head over to CarePosDB and go to the appointment, attributes, and we'll have to modify the status.
00:56:06 So let's edit it.
00:56:08 And I need to say canceled with a double L right here.
00:56:13 Okay, I think this will fix the cancel thing.
00:56:15 So now if I go back and I try to cancel the first one, say testing, and click right here.
00:56:25 Yep, the cancel works and you can see that it actually updates it right here and it also updates it right here.
00:56:32 So at least we know that now the actions are working and our onSubmit is working for the type of cancel, but for some reason it's not working for the type
00:56:41 of schedule.
00:56:42 Let's see why that is.
00:56:44 What are the differences between the two?
00:56:47 Well, we have the type schedule here and we have the type cancel over here.
00:56:52 Everything else is the same, but this one works and this one doesn't.
00:56:57 So let's see, we're passing this type into the appointment model and then we're passing it once again to the appointment form.
00:57:05 The appointment form takes in the type, which is either create, cancel, or schedule.
00:57:11 Then we submit the form and we should get to this point.
00:57:15 No matter what the type is, it should try to submit something.
00:57:19 But if I try to schedule it one more time.
00:57:23 Oh wait, nevermind, now it does it.
00:57:25 So I'm not sure what did we do to fix it, but as you can see, now it actually submits the form with the type schedule and you can change the meeting from
00:57:35 scheduled to canceled and vice versa.
00:57:39 This is great.
00:57:40 So now this is our table.
00:57:43 Everything works.
00:57:44 We have all the fields which you want to have, and we're successfully pulling all the patient data or rather all the appointments belonging to those patients.
00:57:52 So now let's style it a bit better.
00:57:55 I'll close all of the open files and we're now done with the columns.
00:58:00 We can move away from columns and focus on the data table styling.
00:58:06 This data table is still exactly the same as once we copied it from the example code, but now we want to modify the styles.
00:58:14 We can give this div a class name of data-table.
00:58:19 And we can give the table a class name equal to shad-table.
00:58:24 We can also modify the table header by giving it a class name of bgdark200.
00:58:32 And we can also modify the table row by giving it a class name equal to shad-table-row-header.
00:58:42 Scrolling down, in the body, we have the actual table row.
00:58:47 So let's also modify that by giving it a class name equal to shad-table-row.
00:58:54 And then finally below the table, if you remember, we have the buttons for pagination.
00:59:00 So let's remove these class names and add an additional class of table actions and modify the button styling to class name shad-grey-btn.
00:59:14 And instead of saying previous, we can render an image with an icon.
00:59:19 So let's import the next image and let's give it a source equal to forward slash assets, forward slash icons, forward slash arrow dot SVG with a width
00:59:31 of 24, a height of 24 as well.
00:59:36 and an alt tag of arrow.
00:59:38 We can actually remove this next button and duplicate this button above, just below, as the next button will be similar.
00:59:47 So this time we can have the same arrow, but we'll use a class name of rotate180.
00:59:55 Quite a clever way to use the same icon, but then rotate it, right?
00:59:59 And instead of previous, we'll say next page.
01:00:03 And also here we'll say can or get can next page.
01:00:09 And this is it for the table styling.
01:00:12 So let's close this, close this page as well, and check out our table.
01:00:19 This is much better.
01:00:21 On the left side, we have the left pagination.
01:00:23 On the right side, we have the right.
01:00:25 The table now actually takes in full width of the screen, or at least the central part of the screen.
01:00:31 It looks great and it just blends into the dashboard.
01:00:34 All of the fields have enough space, the ID, the patient name, the status, appointment, doctor, and then the actions.
01:00:42 And we can choose what we want to do with each one of these appointments.
01:00:47 So for example, with Layla, we can schedule her.
01:00:50 And for example, let's do it 30 minutes earlier and say schedule appointment.
01:00:55 And once again, for some reason, it doesn't want to schedule it.
01:00:59 Interesting.
01:01:00 Let's try to cancel it instead.
01:01:02 I'll say test.
01:01:05 This one works, now both are canceled, but unfortunately with the schedule...
01:01:10 Oh, now it does schedule it.
01:01:12 Very interesting.
01:01:15 But what if I try to move it and then schedule it?
01:01:19 Yep, still works.
01:01:20 So it looks like if it was in the pending state, it had problems changing the state once again, but otherwise it works perfectly.
01:01:30 Yeah, definitely something to keep testing further.
01:01:33 So why do you say that now that we have actually implemented the admin dashboard, we go ahead and create another patient,
01:01:41 like from scratch, register it, add all of the details, and then that other person can also schedule some appointments.
01:01:50 Let's do that right away by going back to the homepage and let's register as another patient.
01:01:56 In this case, I'll do something like John Doe.
01:02:00 Enter the John Doe at jsmastery.pro email, and I'll use a random phone number right here and click get started.
01:02:11 This just created John in our authentication database, but now we want to add some additional information, such as the name,
01:02:19 that's going to be John Doe once again.
01:02:22 And again, we can remove these fields as we already have some of the info, but for now that's okay, just to verify it.
01:02:29 We'll also add a phone number.
01:02:31 We can quickly add a date of birth, occupation, and all sorts of these things.
01:02:37 This is looking great.
01:02:38 Like we can actually so easily go ahead and fill all of this important information.
01:02:43 And it feels like a very modern healthcare application.
01:02:46 Okay.
01:02:47 Let's enter some more information.
01:02:49 We'll do none and none.
01:02:52 And we also have to choose the ID type.
01:02:54 I'm going to do one, two, three, four, five, and we need to upload a document.
01:02:59 I'll use this fake ID one more time, consent to the treatment and get started.
01:03:06 Okay.
01:03:07 It led me to create a new appointment.
01:03:09 So let's do it with, for example, Aliana.
01:03:13 Let's do it in two days at about 5 PM.
01:03:17 I'll say something like, I'm just bored.
01:03:22 No notes this time and create an appointment.
01:03:26 There we go.
01:03:26 The appointment has been successfully created.
01:03:29 It has been scheduled and we can also do another one.
01:03:32 So let's do something like I'm not feeling well.
01:03:37 If I don't select a doctor, you can see how our validation works.
01:03:40 Let's go with Jane.
01:03:43 And let's be so boring and also select another one.
01:03:47 I'm going to do one with Jasmine and say, again, just bored.
01:03:53 And let's click create appointment.
01:03:56 This is great.
01:03:57 Now in a new tab, I'll open up localhost 3000 one more time, and I'll head over to the admin page.
01:04:05 Once again, we're automatically authenticated because we have filled in the verification OTP not that long ago.
01:04:12 And now we can see all of these new appointments.
01:04:15 So let's just go for the schedule and see if my hypothesis is true.
01:04:20 The pending ones for some reason cannot be scheduled, but a canceled one can.
01:04:25 Let's give it a shot.
01:04:26 I'll go here and click schedule.
01:04:29 And it looks like I was right.
01:04:31 I'm clicking schedule and nothing is happening.
01:04:33 But if I click it on a canceled one, it works immediately.
01:04:38 So this now gives us a lot of info on how to further debug this.
01:04:43 Only if a status is pending, then the schedule appointment button is not working.
01:04:49 So let's go ahead and debug it together.
01:04:52 I'll search for all instances of the pending state.
01:04:56 We have some styles, we have some styles, more styles, incrementing the pending count, that's okay.
01:05:04 The only thing that could be serious is this one right here in the app admin where we're checking for the pending state.
01:05:11 But once again, this is just a stat card, so nothing useful.
01:05:15 So let's try to debug it from the inside out.
01:05:18 meaning from the actual schedule button.
01:05:22 So here we have a button on the appointment form that says schedule appointment.
01:05:27 That text is written right here under the button label.
01:05:31 The type right here just changes the styles, nothing else.
01:05:36 The submit button itself, again, is not doing anything in particular.
01:05:40 It is just submitting whatever the form is.
01:05:44 So we must go to the form above where we actually submit the thing, which is the on submit form.
01:05:54 I don't see a reason why this would not run.
01:05:56 So let's click it one more time while keeping the console opened.
01:06:02 right here, and I'll click schedule.
01:06:06 Once I click it, the whole component reruns, but we're not getting into the onSubmit function, which is very interesting.
01:06:14 But any kind of console log outside of it, like in this component, like if we try to console log the appointment itself,
01:06:21 or if I try to console log, well, let's try to do user ID, patient ID, type, and the appointment.
01:06:28 So I'll console log all of these things here.
01:06:32 at the same time, and let's see, maybe that helps us.
01:06:37 If I do that, go back and click here, you can see that all of the values are properly added right here.
01:06:44 Could it be some kind of validation?
01:06:46 Well, yeah, that could be one of the reasons.
01:06:48 Maybe we're just not seeing it on the front end.
01:06:50 Let's check out the appointment form validation.
01:06:54 It changes depending on the type.
01:06:56 Yes, this is good.
01:06:58 So the type in this case will be schedule.
01:07:01 And let's see how the validation looks like for the schedule type.
01:07:06 It's this one here.
01:07:07 So it looks like the primary physician is necessary.
01:07:11 It needs to be at least one doctor.
01:07:14 The schedule is also required.
01:07:17 Then the reason, note and cancellation reason are optional.
01:07:21 So we only need to have a doctor and a schedule.
01:07:25 Okay, that makes sense.
01:07:27 Primary physician, check.
01:07:29 And a schedule, check as well.
01:07:32 Even if I go back here and select somebody else, you can see that it is nicely reflected right here in the appointment, primary physician.
01:07:41 You can see that it actually updates it.
01:07:44 And if I go here and select different time, all of these things do trigger another check.
01:07:51 But still I cannot click the button.
01:07:53 The reason could be in these default values.
01:07:56 Let's see what's happening here.
01:07:58 The cancellation reason says that it can either be undefined or a string.
01:08:02 So maybe we do it like this.
01:08:05 We say, if appointment question mark that cancellation reason exists, then use that or use an empty string.
01:08:13 Now the TypeScript warning is gone.
01:08:15 We can do something similar for the note.
01:08:17 So this is a shorthand method for.
01:08:21 The ternary.
01:08:22 We don't necessarily need to use the ternary operator right here.
01:08:26 Next, let's check out the schedule.
01:08:28 If an appointment exists, we want to create a new date out of the appointment?
01:08:33 schedule, else create a new date out of the, oh, we need to say date.now, so we need to pull the existing date right now.
01:08:44 This could be one thing, one reason why it wasn't working.
01:08:48 And then for the primary physician, if the appointment exists, then use that primary physician, else do an empty string.
01:08:56 Okay.
01:08:56 Let's see if it was really this.
01:08:59 I will remove all the console logs besides this one I'm submitting and go back here.
01:09:05 I will cancel it first, say test.
01:09:12 Okay, so it works now.
01:09:15 I'll try to reschedule the canceled one.
01:09:17 That works, but we knew that worked.
01:09:19 But let's try to do it with a pending one.
01:09:21 So Dr.
01:09:22 Jane Powell, I'll try to schedule on your appointment.
01:09:25 And without changing anything, I'll click schedule.
01:09:28 And now it actually works and it gets scheduled.
01:09:31 So I'm not a hundred percent sure what was messing with it here, but I'm glad we got it fixed.
01:09:37 This is great.
01:09:39 We have all of these appointments right here.
01:09:42 You can see that the stat cards work.
01:09:45 They're getting fetched here.
01:09:46 We can see the statuses.
01:09:48 And this brings us close to the end of this application.