00:09
difficulty record. We're also going to have various other actions for update, delete, and ordering specific to our difficulties as well. So let's go ahead and plop that into a difficulties folder.
00:18
We can create that action, and we should now be done with our terminal for this lesson so we can hide that away. For our difficulties page, so let's jump into our difficulties controller here, uncomment
00:26
our HTTP context, and add in a method. This will show the list of all of the organization's difficulties inside of an index method.
00:34
And from this method's HTTP context, we want to reach for inertia as well as our organization, which we provided from our organization middleware whenever we apply that to this handler and its route.
00:44
We'll have this return back an inertia rendered page at difficulties slash index, and we will need a state for this to return back all of the organization's difficulties.
00:54
So we'll want to get all of the difficulties and then pass that through to our state. To get those difficulties, we can await reach for our organization, and we can either directly
01:03
query those from here using related and then reaching for the query builder, but we're going to need some fashion of this for all of our resources as well, which all derive off of our organization.
01:12
So let's create ourselves a little helper directly on our organization instance by jumping into our model and our organization's model. At the bottom of our file here underneath our relationships, let's add in an instance
01:22
level method called get difficulties, and we'll have that return back this, which is
01:28
our organization record instance, and then reach for this one individual organization's relationship to its difficulties.
01:36
Now with how we have this written at present, you're going to notice that we get a red squiggly for our relationship name, and that's due to how the type is being derived for the model query builder relationships.
01:46
We need to help this out by informing it what its actual type is. So we'll wrap it in parentheses and specify that this is an organization record.
01:55
With that said, our relationship is perfectly happy, and we can now query directly off of this a little bit, and all that we want to do here is add an order by for the order field.
02:03
We'll want something very similar for our access levels and statuses as well down the road. So while we're here, let's go ahead and add that in.
02:10
So access levels, and then we can reach down here and add one for our statuses as well, and then we just need to switch the relationship name.
02:17
So access levels for that one and statuses for that one. If you want to get really fancy, you can extract all three of these methods into a single one
02:25
called something like getResource, accept the relationship key in as a parameter, pass that directly into the relationship, and then kick the results back that way.
02:33
And since we're not setting async on these methods, we're actually returning back an instance of the query builder itself, meaning that whenever we save this, jump back into our difficulties controller.
02:42
Off of our organization, we can now call getDifficulties, and you'll see that we can build directly off of this with additional filters and other various query statements as well.
02:50
So let's add in a count for the difficulties courses, as we'll eventually need that for
02:56
whenever we get around to deleting these difficulties to check whether or not we need to replace the difficulty being deleted with a different difficulty since the relationship here is
03:06
required for courses. Okay. Then in terms of actually applying this into our inertia state, we'll set our difficulties and we want to mutate this to our DTOs type.
03:16
So we'll reach for our difficulty DTO, import that, and call the helper method fromArray.
03:22
This accepts in an array of the difficulty models, so we can pass directly in our difficulties from our query there into the fromArray, and it will return back an array of our difficulty
03:31
DTOs using our difficulty records. While we're in our backend, let's go ahead and jump into our web routes, and let's add in another group.
03:41
So let's do router.group here, and we're going to tell this group to use the off middleware, however, we also want to use our organization middleware in this one as well.
03:50
So we'll reach for our middleware.organization and call that after we've called our off middleware. The order here does matter.
03:56
We do want to verify that our user is logged in before we attempt to reach for their organization.
04:01
Then let's add in a router.get for our slash difficulties route that uses our difficulties
04:07
controller and its index method as difficulties.index. Next let's go create that page.
04:14
Let's scroll down to our inertia folder, go into our pages, right click, new file,
04:19
and we specified that this would be a difficulty slash index.view script setup lang TS, and
04:25
we'll want to grab our props first, so we'll define props and set the type of this to accept
04:31
in our difficulties as our difficulty DTO, and let's move that import into our script. There we go. And it'll be an array of our difficulty DTO there as well.
04:41
And then we'll also accept in our organization. This is being shared from our organization middleware applied to this route. So we'll import this DTO there as well.
04:50
Let's switch our difficulties to a reactive variable called list by passing our props difficulties into it. And let's also command dot and import ref from view there.
05:00
Then let's reach for the form, dialogue, destroy, and on success method coming from our use resource actions composable.
05:08
We'll give this composable here a type of our difficulty DTO for our resource. And then the default state of our form with a name set to an empty string.
05:18
And we'll also have a color and we'll set the default color to 818CF8. Going back a few lessons here, we also want to watch for changes to our props difficulties
05:27
and apply that change to our list. So let's add a watch effect from view that will set our list value equal to our props
05:36
difficulties should that prop ever change. So as we send data up to our controller, it will send data back down, which would update our props difficulties.
05:46
And this watch effect here is just going to sync the actual list value with the change from our props as that happens. Okay.
05:53
Then for our template, let's add in an app head with a title set to difficulties and
05:59
a description set to manage the difficulties of. And then we can switch this to backticks and inject in the organization name there as well. Okay.
06:09
So switch that to backticks and then prefix the description prop with a colon to make that reactive. All right.
06:16
Then for the actual structure of our page, we'll have a div class with full max width
06:20
2XL, MX auto to center it up, BG background, rounded XL and add a padding of four.
06:27
Let's add in another div class, flex items center justified between and margin bottom of three. Okay.
06:35
Here we'll add in an H1 with a class of text 2XL font, bold and PX4, we'll give this a title of difficulties.
06:42
And then we'll also add a button into this little heading area as well with the size of small.
06:48
And we'll set the variant to ghost that consists of a plus button from loose side view next
06:54
with a class of width three, H3 margin right two. And this button will say add difficulty. Then we click this button.
07:03
So at click, we want to reach for our dialogue and open it up again, since we're creating using our dialogue, we won't pass a resource nor state in, we'll just let the defaults ride there.
07:13
Then we'll want to actually list our difficulties, which we'll circle back to here momentarily. Before we do that, let's go ahead and just add in our form dialogue with a form input
07:24
label of name, view model of form name, and then pass in the error form errors name and give that line there a copy and a paste.
07:34
Except for this one, we want to specify that the type here is going to be color so that we have a little color picker. The label will be color. The view model will be color, and then our errors will be color as well.
07:44
So those are the two fields that we need. Now we need to fill out the props of our form dialogue. First, the resource text should be difficulty.
07:52
Then we'll want to view model the open flag and bind that to our dialogue is open. We'll set the processing equal to our form processing flag.
08:02
And then we switch this to now emit and at create when creating, we can have that go ahead and use our form to post up to a route we will create here in a little bit called slash difficulties.
08:12
And then of course we want to bind in our on success handler to that as well. Okay, let's now circle back and list out our difficulties.
08:20
So we're going to want a UL with a class flex, flex all. And inside of here, we'll have an LI with a class flex item center, justify between
08:29
rounded MD, PX3, PY 1.5, and then on hover, we'll add in a BG slate 100, set a duration for that to 300.
08:38
And we'll also apply in a group modifier to this as well. Inside of here, let's add in another div with a class flex item center gap of four.
08:47
We'll add a div class with three H3 rounded full BG slate 100, which will serve as a fallback background color.
08:56
And then we can inject in a style with the actual background color, which will have precedence since it's in the style tag and the value here will be our actual difficulties color.
09:05
So we'll end out that div and we're going to want to loop over our items to get access to that.
09:10
So on our LI call V4 to loop over each item that we have in our list, set a key of this
09:16
to our item dot ID, and we can now apply the background color from our item color.
09:22
Then directly next to that color, we'll have a span class font old and add in the text from our item name.
09:29
And then lastly, we'll have an additional span with the class text, small text slate 400.
09:35
That will add in a little default tag that will display V if the item is the default item. And we'll set the default item to always be the item that has a sort of zero inside of
09:45
our database. All right. That should be all that we need for right now in terms of our list. Let's go ahead and jump into our browser and we should not be able to click on our difficulties
09:54
to see a list of our difficulties, which right now we see a list of the default difficulties that we stubbed whenever we created our organization.
10:02
And easy is our default difficulty because it's the one that has a sort of zero. Let's go ahead and click on our add difficulty just to make sure that the pop up shows up. OK.
10:12
And it sure does. So we have a little color picker here allowing us to pick whatever color it is that we want. I'd really prefer for this color to also allow and accept in a hex value.
10:20
So let's add in a text input beside this little color picker here. So let's jump back into our text editor, go into our components, and we can bind this directly into our form input.
10:30
Just as we have with our V if type group and then V else on the input, we can add in an additional div with a V else if type equals color.
10:40
We'll add in a class to this div of relative with full and items center inside of here.
10:45
We're going to want our input with a view model of the internal value, a type of color,
10:51
a class of absolute start to inset y2 with 6h6 and set it to be rounded.
11:00
We'll also add in the disabled attribute if the disabled flag is truthy. This will serve as the actual color picker, which is what we saw in our browser right
11:09
here, allowing us to click on it and select any color that we want from a wheel. So we'll still have that via this input here, but we also want to add in a text based input
11:18
allowing a hex value so we can copy our text based input and paste that directly below our color picker input.
11:25
And then we just want to get rid of that V else and add in a class of padding left 10 since we have this input up here absolutely positioned.
11:34
And so that we don't have two color inputs, we want to remove the type from the one that we want to be text based. All right, let's jump back into our browser now, give it a refresh and hit add difficulty. And awesome.
11:44
So now you can see that we have a color picker that if we click on, it will pop open a color wheel. And then as we change that color wheel, you'll see that the hex value in the background is updating along with it.
11:52
In addition to that, we can also type in any hex value that we want here and then we'll update accordingly, which leaves us with needing to add our post route handler for our difficulties controller.
12:02
So we'll add an async method called store that uses the HTTP context from it. We'll grab our request response and organization.
12:10
And the first thing that we're going to want to do is get back our data from the validator by calling request validate using. And we do need to go create a new validation file for this.
12:19
So we'll jump back into our terminal. Let's do node ace make validator and we'll create one called difficulty. I can't spell, but that's okay. We'll hide that away and I'll just go ahead and fix the name of it.
12:29
So we'll go into our validators. I'm going to click on that enter so that I can rename it now to difficulty. There we go.
12:35
So we'll just want to export const difficulty validator equals vine compile vine object.
12:42
We'll have our name as a vine string with a max length of 50. And then we'll have our color as a vine string with a max length.
12:52
And I think we gave this field 20 characters just in case we want to do like RGBA or HSL in the future. But for now we can go ahead and restrict it to hex based values. And that should be our validator.
13:02
So we'd jump back into our difficulty controller and reach for that validator right there.
13:07
As for our store difficulty action, we'll call the handle method and pass in our organization as well as that validated data.
13:16
Afterward we can return response and redirect the user right on back as this will most likely be coming from that difficulties page.
13:23
For our store difficulties handle method, we'll set our params to accept in our organization
13:28
as type organization from our model as well as data of type infer from by GIS type of our difficulty validator.
13:36
Extract our organization and the data and then return organization related difficulties dot create and then merge that data in.
13:46
Now in addition to the data that we're providing from our form, we also have our order field.
13:51
So we're going to need to discern the next order for the difficulties on the organization.
13:57
So we'll do const last difficulty equals await, reach for our organization and query
14:03
its related difficulties and query from it ordering by the order in a descending fashion.
14:10
Then we just want to grab the first result, not first or fail in case the user clears out their difficulties and doesn't have any, but just first.
14:18
Then we can set that order using the last difficulties order and we can do a ternary here in case we didn't get anything back.
14:24
So last difficulty ternary, if it's truthy, we'll use that last difficulty order incremented by one so that we get the next order for this new item.
14:33
Otherwise, if we didn't get a last difficulty, then we just want to set it to zero. Give that a save. And lastly, we just need to bring this up to our routes.
14:39
So let's jump into our routes web router post slash difficulties. Use our difficulty controller again.
14:46
It's store method now is difficulties dot store. Give that a save and we should be all good now to jump back into our browser. Add a difficulty. I'll just call this test one.
14:56
Click add difficulty. And there we go. It added it in. But not only did it add it in, it added it to the end of our difficulties list.
15:03
If we add in now test two, let's click on our color picker, switch this to something like a red or something. Close that out. Add our difficulty. There we go.
15:11
We now have a red color instead of that default purple.