In this chapter, we are going to implement the meetings data table. This will include modifying the procedures to include the duration column and inner join on the relevant agent for that meeting. We are also going to make the existing data table component reusable and we are going to add the empty state component for the meetings view. Let's start with modifying our procedures for my meetings page. So let's go inside of source, modules, meetings, server, procedures.
Before you start writing anything make sure that you're on your default branch. Go ahead and find the GetManyProtected procedure. And we are now going to modify the await database here, specifically the select. After we spread all the columns from the meetings schema, let's also include an individual agent relevant for this meeting, which will use the agents schema. You have to import that from database schema.
Then while you're in the imports, also import SQL from Drizzle ORM. Now, go ahead and add a duration here. The duration will be calculated by using the meetings schema, started at and ended at timestamps. And we're going to use pure SQL. So open SQL like this.
You can give it a type of number. And in here, write extract epoch from open parentheses. And then make sure you don't misspell your columns so you can copy them from here. EndedAt minus startedAt like this and then simply add dot as duration like this. After that let's go ahead and after we select from meetings let's add an inner join here for the agents and we are specifically going to join all agents that have the meeting ID.
My apologies, all agents that were used in our meetings. So meetings.agentID, agents.ID. That's what we are matching. So for each meeting that will be iterated here in the select, we're going to find its relevant meeting ID and we are going to search for an existing agent ID. And we are only going to render the meetings which were successfully matched with their agent.
That's why we're using an inner join. If we were to use a left join, then we would be okay with an agent being null. But that's not the case for us because the agent is always required. So inner join is the correct usage for this. Great.
And now I think we should also add this to our total simply so it is 100% accurate about the amount of data that we are working with here. Great. So now we have successfully modified our procedures. And we can go ahead and render that inside of a data table. So let's go ahead and let's copy the data table component from modules, agents, UI, components.
And let's copy the data table and let's actually add it to our global components right here. Because it is different enough, it is generic enough that it can be used multiple times. So now that we have pasted it inside of here, we can go inside of source, modules, meetings, UI, views, meetings view and we can now use it here data table from components data table. The only thing that's missing here is the actual, well, columns, right? So let's just pass in the items here and now we have to develop the columns.
So let's go ahead and let's copy the existing columns from agents here. So in UI components, we have the columns here and let's paste them inside of our meetings UI components. So now we have columns here as well. So what I want to do now is I want to go inside of my meetings types and I will copy meeting get one and I will replace it with getting meeting get many and let's use meetings get many here as well. So now instead of my columns I will be a bit more specific.
So make sure you are inside of your newly copied columns in the meetings. And import meeting getMany and then in here specify number. So actually, let's go ahead and do type meeting, meeting getMany number like this. Or maybe I'm doing something wrong here. Oh yes, so getMany here should be items.
My apologies. And yes, then you can use the number here and then this number will just be a single one of these values here. The reason I'm using MeetingGetMany and not MeetingGetOne is because these columns will be loaded using the GetMany procedures, right? And instead of this GetMany procedures, we just modified the select. The select here is different than the one from get one.
The get one doesn't have it, so it would be an incorrect column data. So that's why I'm instead using meeting get many and simply specifying a single item from there by using the number operator here. Great, so now that we have this, let's go ahead and let's add everything that we will need from here. Let's start by adding npm install datefns. Like this.
Once you have added datefns, just go ahead and include it here. Let's also add npm install humanize duration with legacy period depth as well. Let's import that here. There we go. Then we are going to need a lot of icons from Lucid React besides these here.
So let's go ahead and add the following icons. Circle check icon, circle X icon, circle arrow up icon, clock fading icon, and corner down right icon, and lastly, loader icon, like this. We are going to need the CN libutil from here, and that's going to be it. So now let's go ahead and before we start the columns let's implement a function format duration here seconds number and return humanized duration here, multiply the seconds by a thousand, largest will be one, round will be true, and units here will be hours, minutes, and seconds. You're going to see how we're going to use this function in a second.
Great. Now let's go ahead and let's define a const status icon map. So depending on what status the meeting is in, we're going to display a different icon. If it's an upcoming, it will be clock arrow app icon. Active, this one.
Completed, this one. Processing, this one. And cancelled, this one. It is very important that you don't misspell these ones. And if you aren't sure, you can go inside of your schema here and find the meeting status.
So you can copy them from here. Make sure you don't misspell them. And now what you're going to do is create another map called status color map. So depending on upcoming, active, completed, processing, or canceled you will have specific colors. An easy way to tell you've done it correctly without typos is to click on one of them and the other one will highlight, meaning that it's exactly the same.
There we go. So let's go ahead and do the upcoming one first. So it's going to have a BG yellow with 20% opacity. It's going to have a text of yellow, whoops, a text yellow 800, and it's going to have border yellow 800 with 5% opacity. And the other ones will be very similar, just different colors.
So the active will be background blue with 520% opacity, Text blue 800 and border blue 800 divided by 5. For the completed one, same thing but using the emerald. For the cancelled one, my apologies, for the processing it's going to be a rose and the exact same values as the ones above. The only one that will be slightly different is the processing one. My apologies, so I have reversed these two.
So instead of processing here this will be cancel like this. Canceled one will be using the rose color and then change the last one to be processing like that. This one will use gray 300 with 20% opacity and the rest will be the same. So you can pause and copy. And now we are ready to build our columns.
Let's start with the accessor key name, which will now be meeting name. The flex flex call gap y1 will be the same. And then inside of here, let's immediately add a span here, which will render the row.original.name with a class name, font, semi-bold, and capitalize, like this. Now go ahead and remove this div here so we can immediately work on this one, flex-item-center-in-gap-x2, rendering the corner down right icon. Instead of this flex-item-center-gap-x2, add a new div with a class name flex-item-center-gap-x1 like this and simply encapsulate the corner down right icon and the span.
And this will render the agent.name. And then, outside of this div, add a generated avatar component. This will use a variant of bots neutral seed row original agent dot name and the class name of size four. And after that, add a span element here, row dot original dot started at if we have it, we're going to format that exact field. So started at using mmm and D format otherwise an empty string and give this a class name of next small and text muted foreground Now let's go to the second cell, which will be the status, and a header will be status.
And inside of here, let's go ahead and do the following. Let's remove the cell function like this and let's open the curly brackets. And first let's grab our icon, which will be statusIconMap row original.status as key of type of statusIconMap. And then let's go ahead and let's return a badge element here. And inside we are going to render that icon.
Let's go ahead and append some props here. So variant will be outline. Last name here will be CN. Capitalize. Go ahead and target an SVG here, give it a size of 4, and text muted foreground.
And then let's go ahead and check if status color map has an original status here, which should be a key of type of status color map. So now our icon will automatically be generated depending on the meeting status as well as our color here for that badge. And for the icon here, we can give it a class name, CN, row original status is processing. And in that case, simply do animate spin. So it shows a spinning icon.
And let's render the row original status here, like that. And now let's go ahead and do the last column here with accessor key duration. And let's do header of duration. And I think I have to do this here my apologies so I add a comma here because this curly bracket is the cell function So make sure you are doing it in the proper indent like I have now. And let's open a cell, let's destructure the row here, and let's return another badge here like this.
Now let's go ahead and give this badge the following variant of outline. And let's copy the class name. You can copy it from here like this. So last name, capitalize, and SVG size for flex items, center and gap x of two. And inside of here, add the block fading icon, give it a class name of size, my apologies, text blue 700, and check if we have row original duration, and in that case, format duration, using the row original duration as the prop.
Otherwise, simply render no duration, meaning this meeting has not been started yet. Now let's go and see what we have not been used. So we didn't use humanized duration. Not true, my apologies. We did not install the types for humanize duration.
There we go. So this is how the full command looks like. And let me just quickly show you my package JSON. So humanize duration, and date FNS. There we go.
Perfect. So now we can go back inside of the meetings view. And we can pass the columns here to be columns from that that forward slash columns and now when you try to load the meetings it should have a complete new look what we have to do now is add some padding and it seems like something's wrong with the colors here. So let's just quickly go inside of here, and let's see what's going on. So status color map, upcoming, we definitely have that.
And this is the status of upcoming. Status color map here. Let's just see, is there something we're doing incorrectly here? I think everything's okay with our code because the icon is rendered correctly, right? This is the correct icon we assigned, so this line of code should work just fine.
So I'm going to debug this in the next chapter. It could be because of the Just-In-Time compiler or Tailwind, maybe. I'm going to explore a bit. But anyway, let's go ahead and focus on wrapping this chapter up by going back inside of the meetings view here and give this div a proper class name of flex1, adding bottom of 4, px of 4, medium px of 8, flex flex column and gap y of 4. There we go.
Perfect. So in the next chapter, we're going to add the filters and you can go ahead and try something here and click Create. And there we go. You can see our agent right here. Perfect.
So now it looks very similar to our agents field. Let's just wait a second for this to load. And the style is matching, the theme is matching, and our data table looks the same. I just want to change one thing, and that is to make DataTable component reusable here. And also we have to add the EmptyState component.
Yes, I forgot about that. So let's go ahead and do the following. Inside of Source, Modules, Agents, inside of UIViews, AgentsView, Let's go ahead and let's import the data table, not from components data table, local ones, but instead from the global ones, using add components data table. So it's reusable, which means that we can now go inside of the modulus agents UI components and we can delete the data table from here. We no longer need it.
And one more thing I want to fix here is my columns inside of the agents. So let's go inside of the in the types first and let's go ahead and prepare agent get many agents get many agents get many items like this then go inside of UI components columns. And in here, when you use agents get one, use agents get many number. And nothing should change the agents view should work perfectly fine. But we are now using the proper getMany inference.
And now we also have to add the empty state. So we can copy this from the agent's view right here. And let's go inside of meetings, UI, Views, meetings view. And let's just add that right here. Import empty state from components, empty state.
And now we just have to change this to create your first meeting like this. And let's add a description. So the description I will use is the following. Schedule a meeting to connect with others. Each meeting lets you collaborate, share ideas, and interact with participants in real time.
Perfect. So if you want to see that in action, go inside of your meetings here and go inside of your Neon console or Drizzle Studio and simply delete all meetings. And then do a refresh here and you should see this, create your first meeting. Amazing! That's it.
Let's go ahead and merge this pull request. So 18 meetings data table. I'm going to create a new branch. 18 meetings data table. I'm going to stage all changes.
There we go. I will write a commit message. And I will click Commit and Publish Branch. And once the branch has been published, Let's go ahead and review the pull request. And here we have this chapter's summary.
We introduced a new table view for meetings, displaying meeting name, status, agent details, and human readable duration. We added empty state messaging to guide users when no meetings are present. We enhanced meeting data by including related agent information and computed duration for each meeting. We improved the agents table typing for more accurate data representation. Perfect.
So in here I don't think there's anything too useful in the sequence diagram that we haven't seen before. But as you can see, it immediately added to the diagram that we now join the agents and compute the duration, right? So It's not just repeating the same diagrams. It's actively reading the context of everything we've done before. And you can see how it detected all related PRs, because we previously added these kinds of things.
So it knows that this is similar. So it tells you, this is possibly related. You might want to take a look at that. In a real world example, this would be extremely useful. And we have one comment here to improve the duration SQL.
I'm not a SQL expert, so I don't know what to tell you if this is good or not. If you want to, you can try it out. But I will stick with what I know worked for my project right here. Great. So let's go ahead and resolve this conversation and let's merge this pull request.
And after we've merged the pull request, let's go back to our main branch here and let's click on the synchronize changes button and click OK. And inside of your source control here in the graph you will see 18 being merged. Amazing, amazing job! That marks the end of this chapter and see you in the next one.