So I was experimenting with our summary endpoint and I noticed we do have a bug when it comes to calculating the percentage change of the last period. As you can see I have added a bunch of transactions and I have a bunch of transactions in the last period but still all of my expenses change percentage is zero. So I've noticed a bug inside of our summary endpoint. We don't use the last period start and last period end. So go ahead and find last period, fetch financial data, and instead of start date, we have to use the last period start and last period end, like this.
So now if you have enough transactions, there is still a chance that you still get zero and it's correct, right? Don't worry. But I have added a bunch of transactions and there we go. Now you can see exactly how much it changes in percentages here. Great.
So if you're wondering how I filled with this much information, it's thanks to a seed script. So we're gonna go ahead and create a seed script now so you can copy this from my source code. Go ahead inside of your scripts and create seed.ts right here and copy and paste this from the source code. You can also pause the screen. I'm gonna go slowly here if you want to take a look at what's going on, but there is a lot of change going on here, right?
You don't need the seed script at all, but if you just wanna get a bunch of data, you can use this seed script. Only thing you have to change is the seed user ID. So let's go ahead and obtain that. So you're going to go in your side of your clerk dashboard, go inside of users, find your user, the one you want, and copy that user. And then go ahead inside of here and replace this seed user ID with that ID.
And then let's go inside of our package.json and let's go ahead and copy and paste this. So you didn't have to do this, right? You can continue the tutorial without it. It doesn't matter. But if you want to have a bunch of financial data, you can use the seed script.
And now let's go inside of the terminal here, let's shut down the app and let's run bun run database seed like this. And this will just, if there's no error that means it successfully created a bunch of data. So if I refresh here I should have some different numbers here because it reset everything. There it is. Now I have different numbers and I have all these categories.
Great! So I think I've noticed one more bug here inside of my summary And that is this calculate percentage change. So I have a type of calculcate, so calculate. This is how it should be. So I'm just gonna find all instances and replace that typo.
And let me reload my window to fix the TypeScript error. There we go. So some slight improvements on our Summary API endpoint. And now let's go inside of our app folder, dashboard-page.tsx. But actually before we can do anything here let's go ahead and let's create our features.
So I'm gonna go ahead and create the summary feature here, API, and I'm going to create use get summary dot ds here and we can copy the transactions so api use get transactions we can copy this because we're going to need this params so paste that inside of here Let's rename this to use get summary Like this We can remove this to do key Well, we can leave it actually, yeah, because we're also gonna use those here So we needed to use search params, we need the from to and account ID but our query key here is summary and we are calling the summary here and the query should be fine because our summary endpoint accepts the same things from to and account ID And this will be fail to fetch summary right here. And now we have to modify the data a little bit. So the data is an object. So we're going to go ahead and spread the data here. And then we're going to add income amount to be convert amount from mill units.
Data income amount. Then we're going to do the same thing for expenses amount and then we're gonna do the same thing for remaining amount And let's see what else we have to do. So we have to do the same thing in the categories. So let's go over categories and write data.categories.map. We get the individual category.
And we return an immediate object where we spread the existing categories but change the value to convert amount from milli units into category dot value like that and We have to do the same thing for the days. So data.days.map, we get the individual day, we open and return an immediate object, we spread what exists in the day and modify the income to convert amount from milli units. So day.income, and we do the same thing for the expenses for that day. That is it. Make sure you've added convert amount from milli units here.
Great! So we have our use get summary here and now we can go ahead inside of the app folder dashboard page.tsx finally so let's remove use client let's remove the button let's remove everything here we're not going to need pretty much anything for now. We can change this to be our dashboard page. And let's give this div a class name of max-width-screen-to-excel-mx-alto full-width padding-bottom of 10 and minus margin-top of 24. And then inside of here, we're going to use the data grid component so let's go ahead inside well we can go ahead and create that in the components for example So let's go ahead and create data-grid.psx like this.
Let's mark this as useClient because the parent is a server component, right? It doesn't exist. So let's do export cons.data.grid here. Let's return a div.data.grid. Let's go ahead and let's import data grid from components data grid like this.
So now on my localhost 3000 let me just refresh. There we go. I should have data grid written right here. So what we are going to do is we're going to go ahead and we're going to create a component called data card but just before we do that let's go ahead and give this class name a grid grid calls one lg grid calls three gap of eight adding bottom of two and margin bottom of eight, like that. And Now let's go ahead and let's add the following hooks.
So let's add useSearchParams. UseSearchParams from next navigation. This is the one. And let's get the params. UseSearchParams.
Let's get to. And let's make it params.getTo or undefined and we're gonna do the same thing for from like this, there we go And now let's go back inside of the DataGrid component. Oh, we already are here. So let's go ahead and do the following. We're going to create a little util in our libs, in our utils, my apologies.
And the util will be called format date range so it's gonna accept a period which will be an optional period and we're gonna define type period here so that's gonna have a from which can be either a string or date or undefined. And the same thing will be to. So that's the type period. And now let's go ahead and open this method. And inside of here, let's do const default to, to be new date, const default from to be sub days from date FNS, default to 30.
So the same thing we keep doing on the backend, right? We are creating a default range. So make sure you've imported sub-dates because we just added it here. Then we're gonna check if we don't have a period from. The format date range is going to return a label, which will simply say format, again, from date FNS.
Make sure you've added this. Okay, right here. So it's going to say format default from in a format of LLLBD and then we're going to add to so this will be like a to write from to format default to in the same LLLBD, Y So we also want to add the year at the end. No need to, no point in showing the year here so we can save some space. We can only show it in the last one.
Like this, there we go. So I expanded so you can see it in one line like this. There we go. And now we can copy and paste this. And this if clause will say, if we have period.to right here, so this will then do period.from and period.to.
Again this is how it looks like in one line and the last one will be the simplest one return format period.from and llbd, y like this. So we are creating a readable range. So the user knows which dates are selected. So const date range label, We'll now use format date range from Libutils and we're gonna pass in our period, which will be to and from, like this. Just make sure you've added this.
And I think you can already try this out. So if I render this here, there we go. So the last 30 days from April 10th to May 10th and today is May 10th or 10th of May right there we go so now we have to create a component called data card it's going to be a self-closing tag and it will accept the following props. The title, which will be remaining. Let's see, it looks remaining.
Then it's gonna have value, which will be data, question mark, remaining amount. Oh, I forgot to add any data here so we are going to have of course use get summary from features API use get summary and in here we can get our data. There we go. So now data should have the remaining amount. There we go.
Perfect. And then we're going to have the percentage change which will be data and it's in this case it's just gonna be the remaining change icon will be FAPiggybank so let's go ahead and add this clear bun add react slash icons So we need that alongside our Lucid icons because this one's have a specific look. So let's do bun run dev. And then we can import FA piggy bank from react icons FA like this. There we go.
And then we can import FAPiggyBank from react-icons.fa like this, there we go and variant will be default and date-range will be our date-range label there we go, So now we have to create this data card component. So let's go ahead inside of components and let's create data card dot dsx. Let's go ahead and let's create the export const data card here and let's return a div data card Let's go back inside of the data grid and we can now import data card from .slash data card, but I'm going to change it to use components data card. There we go. Now we have to create the props for the data card.
So for that, we are also going to have to create the variants. So I'm going to be using the inspiration from Shazzy and UI. You can see how inside of components UI button, this is how they create the variants, right? Using this default destructive outline. So I want the same thing.
So let's go ahead and let's import what we need so we are going to need the CN library from libutils and we are also going to need the variant props from class Variance Authority and CVA we're also going to need icon type from react icons but no need to go to slash lib you can just import it directly like this. All right I think that should be okay for now. Now let's create a box variant which will be CVA. Let's give it some default props rounded and D and padding of 3. Open an object and write variants and then inside of here we are going to have the default variant.
And that's going to be background blue 500 with a 20 opacity. Now the second one will be success, which will be emerald. And the third one will be danger which will be rose and the last one will be warning which will be yellow so different variants for our card so they can have very different backgrounds Now let's set the default variants here. Variant, default. So we choose the one from above, which we created.
Great. Let's copy and paste this now. And we have to do the same thing for the icon variant. So you're gonna see in a moment where we're gonna use this. So for the icon, the classes are different.
This will be size six by default. And inside of here, we're just gonna be using the fill property and we are not going to need the opacity here like this. And the default variant stays the same now let's create a type box variant it will be variant props do we have variant props? We do have variant props, alright so variant props type of box variant copy and paste this into icon variants. That's going to be a type of icon variant, like this.
And now finally we can create an interface, not a type. It needs to be an interface, which will be data card props extends box variants and icon variants. So this way we are going to infer all of these things into TypeScript props, right? So let's go ahead and give it an icon of icon type, a title of string, a value of an optional number, a date range of a string and a percentage change of an optional number. Let's assign all of those things here so data card props and I think we should not be having any errors and we are not perfect So now we are ready to style this.
Let's just extract everything here. So icon will be remapped to icon with capital I so that we can use it as a component like this icon. Right? So that's why we have to rename it here. This is called an alias.
And then we're going to add a title and a value which we will default to zero a variant date range and percentage change which will we will also default to zero here instead of using a div we will be using a card from .slash UI card or components UI card let's go ahead and let's extract everything we need from our card here so we need card content card description card header and card title that's everything we need here So let's go ahead now and let's go inside of here and let's add our card header component. Like this. And let me refresh my localhost so that we can actually see the changes that we are developing. So CardHeader will have a class name of text to Excel and line clamp one let me just refresh this again there we go so remaining I have a typo let's go inside of the data grid and fix this remaining there we go so this will have a title then we're gonna have a card description here which will render the date range to tell the user where it's coming from. So this will have a line blank one like this.
There we go. Alright and let's go ahead and do the following. Let's create a div around the title and the description. And let's go ahead and give it a class name of space Y2 There we go, so I want them one below another, perfect And then what we're gonna do outside of this div is another div We're gonna give it a class name which will be CN, so it's gonna be dynamic we already have CN imported here by default is going to have shrink 0 and then we're gonna give it a box variant and passing the variant value inside and it's also well that's it but inside we're gonna use the icon component and for it we're gonna give it a class name again of CN and pass in the icon variant and a variant prop inside. So basically we are going to, this is how you use the class variants authority library.
So you define your variants inside of here and then using this props, you then have something called a variant prop. So in here we don't have it, but you can see how I can extract it here, right? So it doesn't come typed from here. We extend the box variants and icon variants, and we made sure that all of these have the exact same named prop and all the exact same name values. So we can then reuse it in this way.
When we pass in the icon variant to the icon component what's going to change is the fill colors right and by default it's going to have a size 6. So for example what we could do inside of here is also add shrink 0 to the box variant in the default ones like this shrink 0 and then we would not have this at all right it would be even simpler exactly like the one above there we go so you can see how this looks like Now our piggy bank is blue color. So if I go back to the data grid and change this variant to for example, danger, now the piggy bank is red and the icon is filled with red color. Or if I go to success, now it is green. Great.
So I'm gonna go ahead and leave it at default here or you actually don't have to pass it, you can be as explicit or not as you want great! Now I wanna go ahead and I wanna create a count up component here So let's go ahead and do that. I'm gonna go ahead inside of my terminal. I'm gonna do bun add react count up. But since this is an older component it doesn't exactly work perfectly with Next.js and server components and hydration but there is a very very easy fix for that go inside of your components and simply create count-up.vsx mark it as use client, this is very important import count-up from react-countup and export count-up as simple as this, This will solve any problems with this library.
Now let's go back inside of our data card component and let's go ahead and let's import count up from .slash count up or components count up like this there we go so now Let's go outside of our card header and let's add our card content here We already have the card content imported And let's create an h1 element here with a class name FontBold, text to Excel, margin-bottom of 2, line-clamp of 1 and break-all Inside of here we're going to pass in the count-up value It's a self-closed component Let's add preserve value so on every update it doesn't start counting from zero but it just says where it is or it starts counting from the last known value otherwise it will start from zero but it's going to end from the value that we have for this card the decimals are going to be two Decimal places are going to be two. And then we have to pass in the formatting function, which in our case is gonna be our format currency method, which we already created in libutils. So just make sure you import that. So we already use this throughout the project in the transaction, in the columns, right?
There we go. So we now have this. Let's just refresh the local host so we see it running. And now outside of the h1 element we're gonna add a paragraph here and we're gonna add the following so we need to create another lib called format percentage so let's go inside of our utils here I don't think we have format percentage, we don't. So let's go ahead and create that.
So export function format percentage. It will accept a value which is a number and options add prefix, which will be an optional Boolean. And this is a comma here, my apologies. And by default, we're going to give it the value of add prefix to be false. Now inside of here, I'm going to write const result and we're going to use a native API.
So new intl, number format, and us, open an object, style, percent, dot format, value divided by a hundred. And now that we have the result, if we have options.addPrefix and value is higher than zero, in that case, we're gonna show this as plus result. Otherwise, we're just gonna return the result because it doesn't matter. The minus will always be shown, regardless if we show the prefix or hide it. So now we can go ahead and use the format percentage from here.
So just make sure you've imported it from libutils. And inside of here, we're going to pass in the percentage change from our props here. And we're going to write from last period here, like this. So let's refresh this as well. Zero from last period.
And there we go. You can see our count up component here working let's give this a class name of CN text muted foreground by default text small by default and we're gonna have line clamp one. And then we're gonna say, if percentage change is higher than zero, we're gonna mark this as text emerald 500 otherwise if it is lower than zero we're going to indicate that something is wrong like this there we go perfect And now let's go back inside of our data grid component. And we're gonna go ahead and copy and paste this two times. The second one will be our income.
So it's gonna be income amount. And this will be income change. And it's gonna use an icon FA arrow trend up. So let's import this from here. Actually I think this is imported from another one so import FA arrow trend up from react icons FA6 like this and alongside this we're also gonna have FA arrow trend down.
So those two icons. Okay, FA arrow trend up, that is okay. And this one we say expenses. So expenses amount like this. Expenses change.
And this will use the FA arrow trend down like this and there we go. Look at our beautiful cards right here. Currently what looks a bit off is that it stays at zero in the beginning because it's loading. So if you like that you can leave it like that but I'm also going to show you how to create a loading component. So go inside of data card and first of all add a skeleton from .slash uiskeleton here and then go ahead at the bottom and export const data card loading Go ahead and return a card component.
Give it a class name. Border none. Drop shadow SM. And a height of 192 pixels. Add a card header component.
Give it a class name of flex-flex-row-item-center-justify-between and gap-x-4 and I'm just gonna go ahead and speed things up a bit so go ahead and copy well, I copied it just to speed things up so create a div with space Y2 and add two skeletons inside with height 6 and width 24, height 4 and width 40 here and then outside of this div you're gonna go ahead and paste and create a skeleton with a size of 12 and finally outside of the card header you're gonna add the card content with two skeletons one with shrink 0 height 10 and width of 24 and a margin modem of 2, one with shrink 0 height 4 and width 40 like this And now you can go back inside of your data grid component and you can destructure the isLoading from here like this and then inside of here you can write if isLoading return and go ahead and copy this div and inside of here you're gonna add data card loading from components data card So let me just add these two together like this and let's just align that. And go ahead and add three of those.
There we go. Let's take a look at this now. So you can see our beautiful loading tabs now. Perfect. So what we have to do next is we have to add our data charts.
So for days, right? And we have to add one for categories. Great, great job!