[{"data":1,"prerenderedAt":471},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-expenses-system":121,"100-apps-100-hours-expenses-system-next":203,"sales-reps":219},{"items":4},[5,29,49,69],{"id":6,"title":7,"url":8,"page":8,"children":9},"522e608a-77b0-4333-820d-d4f44be2ade1","Solutions",null,[10,15,20,25],{"id":11,"title":12,"url":8,"page":13},"fcafe85a-a798-4710-9e7a-776fe413aae5","Headless CMS",{"permalink":14},"/solutions/headless-cms",{"id":16,"title":17,"url":8,"page":18},"79972923-93cf-4777-9e32-5c9b0315fc10","Backend-as-a-Service",{"permalink":19},"/solutions/backend-as-a-service",{"id":21,"title":22,"url":8,"page":23},"0fa8d0c1-7b64-4f6f-939d-d7fdb99fc407","Product Information",{"permalink":24},"/solutions/product-information-management",{"id":26,"title":27,"url":28,"page":8},"63946d54-6052-4780-8ff4-91f5a9931dcc","100+ Things to Build","https://directus.io/blog/100-tools-apps-and-platforms-you-can-build-with-directus",{"id":30,"title":31,"url":8,"page":8,"children":32},"8ab4f9b1-f3e2-44d6-919b-011d91fe072f","Resources",[33,37,41,45],{"id":34,"title":35,"url":36,"page":8},"f951fb84-8777-4b84-9e91-996fe9d25483","Documentation","https://docs.directus.io",{"id":38,"title":39,"url":40,"page":8},"366febc7-a538-4c08-a326-e6204957f1e3","Guides","https://docs.directus.io/guides/",{"id":42,"title":43,"url":44,"page":8},"aeb9128e-1c5f-417f-863c-2449416433cd","Community","https://directus.chat",{"id":46,"title":47,"url":48,"page":8},"da1c2ed8-0a77-49b0-a903-49c56cb07de5","Release Notes","https://github.com/directus/directus/releases",{"id":50,"title":51,"url":8,"page":8,"children":52},"d61fae8c-7502-494a-822f-19ecff3d0256","Support",[53,57,61,65],{"id":54,"title":55,"url":56,"page":8},"8c43c781-7ebd-475f-a931-747e293c0a88","Issue Tracker","https://github.com/directus/directus/issues",{"id":58,"title":59,"url":60,"page":8},"d77bb78e-cf7b-4e01-932a-514414ba49d3","Feature Requests","https://github.com/directus/directus/discussions?discussions_q=is:open+sort:top",{"id":62,"title":63,"url":64,"page":8},"4346be2b-2c53-476e-b53b-becacec626a6","Community Chat","https://discord.com/channels/725371605378924594/741317677397704757",{"id":66,"title":67,"url":68,"page":8},"26c115d2-49f7-4edc-935e-d37d427fb89d","Cloud Dashboard","https://directus.cloud",{"id":70,"title":71,"url":8,"page":8,"children":72},"49141403-4f20-44ac-8453-25ace1265812","Organization",[73,78,84,88],{"id":74,"title":75,"url":76,"page":77},"1f36ea92-8a5e-47c8-914c-9822a8b9538a","About","/about",{"permalink":76},{"id":79,"title":80,"url":81,"page":82},"b84bf525-5471-4b14-a93c-225f6c386005","Careers","#",{"permalink":83},"/careers",{"id":85,"title":86,"url":87,"page":8},"86aabc3a-433d-434b-9efa-ad1d34be0a34","Brand Assets","https://drive.google.com/drive/folders/1lBOTba4RaA5ikqOn8Ewo4RYzD0XcymG9?usp=sharing",{"id":89,"title":90,"url":8,"page":91},"8d2fa1e3-198e-4405-81e1-2ceb858bc237","Contact",{"permalink":92},"/contact",{"items":94},[95,101,107,113],{"id":96,"title":97,"url":8,"page":98,"children":100},"8a1b7bfa-429d-4ffc-a650-2a5fdcf356da","Cloud Policies",{"permalink":99},"/cloud-policies",[],{"id":102,"title":103,"url":81,"page":104,"children":106},"bea848ef-828f-4306-8017-6b00ec5d4a0c","License",{"permalink":105},"/bsl",[],{"id":108,"title":109,"url":81,"page":110,"children":112},"4e914f47-4bee-42b7-b445-3119ee4196ef","Terms",{"permalink":111},"/terms",[],{"id":114,"title":115,"url":81,"page":116,"children":118},"ea69eda6-d317-4981-8421-fcabb1826bfd","Privacy",{"permalink":117},"/privacy",[],{"description":120},"\u003Cp>A composable backend to build your Headless CMS, BaaS, and more.&nbsp;\u003C/p>",{"id":122,"slug":123,"vimeo_id":124,"description":125,"tile":126,"length":127,"resources":128,"people":132,"episode_number":136,"published":137,"title":138,"video_transcript_html":139,"video_transcript_text":140,"content":8,"status":141,"episode_people":142,"recommendations":156,"season":186,"seo":201},"cb4e067f-9507-4e18-ab9a-435565f9e653","expenses-system","893800625","Expensify is an expense management tool that tracks receipts, rolls them up into business reports and documents, and handles the approval workflow. Bryant has one hour to build it (or die trying).","48dba4a7-4bf0-45df-9b3d-4de5ffa8b98a",63,[129],{"name":130,"url":131},"Mindee OCR API","https://www.mindee.com/",[133],{"name":134,"url":135},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",1,"2023-12-22","Mission: Expensify Clone","\u003Cp>Bryant Gillespie: Hi. Bryant here. Welcome to the inaugural episode of 100 apps in 100 hours, where I will take some of your favorite apps and try to recreate them in under 1 hour or die trying. Hopefully not dying, but you get the idea. And first on our list today is Expensify.\u003C/p>\n\u003Cp>So we're going to build an Expensify clone. Expensify is expense management. And let's actually I'm not really sure I like the word clone here. Let's call this, a tribute, like the Tenacious d song. This is just a tribute because it would be very difficult to build all the functionality of Expensify in 1 hour.\u003C/p>\n\u003Cp>Obviously, they've built an entire business and they probably have thousands of developers, but let's get as close as we can. So when I think about Expensify, let's just go to their website, I think of the the first thing I think of is scanning the receipt, taking a picture of it, and it does the automagic stuff, where it automatically fills out expense reports for me, which, to be honest, is kind of a pain in the rear anyway. So, that is one thing that we're going to be concerned with, the let's just map this out. Automagic Receipt Management. We're going to have expense reports.\u003C/p>\n\u003Cp>Expense reports. So we wanna roll up all of those expenses, and, some of those are gonna be billable, And all those may be reimbursable, so we need to track for that. What else? We want to be able to approve expense reports. Seems like a good list for getting started.\u003C/p>\n\u003Cp>Alright, so I'm gonna pull up my handy tool of choice, Directus. So Directus, for those of you who aren't familiar with it, is a back end toolkit that allows you to build pretty much anything or everything, and get instant APIs on top of any SQL database. That's the pitch. I swear that's it. Let's actually run the clock, and I'm late on the clock here, but we'll start the clock 60 minutes on the timer.\u003C/p>\n\u003Cp>Let's go. We're gonna hop into Directus, and I am in my data model settings right now. And I'll zoom way in so you guys can actually see this alright. First thing we're gonna do is let's spec out our expenses. So let's just dive into our data model.\u003C/p>\n\u003Cp>We're gonna have expenses as one of our collections, so Directus is gonna create this table for us. And when we go to the optional system fields, we'll probably have a status. Let's go with the sort for the order on the expense report and we'll just select all these system fields because they have some presets that are useful to us. Alright, so we've got expenses, and I'm just gonna use a Expensify attribute. We'll create a folder here for this.\u003C/p>\n\u003Cp>We even give it like a nice green color. Boom. We're gonna organize all that inside our Expensify attribute. Okay. So the next thing, we are going to have an expense report.\u003C/p>\n\u003Cp>Right? We've got to roll up all those expenses somewhere. This is where we'll do it. Okay. Great.\u003C/p>\n\u003Cp>Okay. What else are we going to need? Let's just get started with this functionality. Alright. So, we go back to Expensify and let's try to figure out exactly what their data model looks like.\u003C/p>\n\u003Cp>My favorite way of doing that, short of interacting with the app is searching for their API. So we'll just go to the Expensify API, we'll go to their API reference, we'll just zoom way in on this, Expensify integration server, all you need to integrate. Alright. So request formats, we see what's going on here. Let's look for read and get policy lists.\u003C/p>\n\u003Cp>Maybe create. Okay. I see here's the expense creator. Alright. So, okay, it looks like here's a definition of the expense object.\u003C/p>\n\u003Cp>So reading through this, we've got a merchant, we've got the date of the expense, we've got the amount incense, we've got the currency, that's an integer, that's probably on purpose, three letter code of the expense, external ID, there's a category, there's a tag, is it billable, is it reimbursable, Comments, reports, IDs, tax. Okay. Straightforward enough, let's dive in. We'll go to our expenses and first, let's add our merchant. So we'll just create a new field inside our data model for Directus.\u003C/p>\n\u003Cp>This is just going to be a string type and clear enough, we are going to require value. We've got to have somebody that we got this receipt from. Let's change that to half width. And now let's move on to the amount. So let's do an input field.\u003C/p>\n\u003Cp>Now I could do this as an integer in cents. What I'm going to do instead is just use a decimal for this. So we'll do a decimal, I could change the scale to 2 points and, looks good. Alright. So now we've got the amount for the expense, we need to add a date for the expense.\u003C/p>\n\u003Cp>Expense date, let's just call that. So we'll choose the date time here and I'm gonna pick the time stamp as the type just because that will respect any time zones. I'm in the US. I use the 24 hour format. Or let's not use the 24 hour format since I'm in the US.\u003C/p>\n\u003Cp>No need to include the seconds. That's great. So we got our merchant. We got the amount. We got the expense date.\u003C/p>\n\u003Cp>Let's just do the name of the expense. Is that inside Expensify? Let's take a look. Created amounts, optional tag. Yeah.\u003C/p>\n\u003Cp>I'm just gonna give a name for the expense. Maybe we have some notes for that. Great. We'll add the text area field for that. We don't need any formatting.\u003C/p>\n\u003Cp>Great. Merchant amount, let's do a little cleanup on this. Directus makes it so easy to build apps that look great for your users. Alright. What else do we have?\u003C/p>\n\u003Cp>An expense comment. Got that. So then we have things like, is this billable? Is this reimbursable? So these are going to be toggles inside Directus.\u003C/p>\n\u003Cp>That's the name of the interface as we call it. Let's do is billable. I like to prefix those with is, just keeps things organized. Default value will be false, and looks good. Maybe we want to embellish this a little bit.\u003C/p>\n\u003Cp>So if you go into the advanced mode inside Directus, we can add notes for our users. Is this expense can we invoice this to a client? Great. Alright. Enabled.\u003C/p>\n\u003Cp>Let's just call it billable for the label. That seems a little better. The display is where it will show out show up in some of the different layouts inside Directus when you're looking at a list of records. Yeah. I don't see any need to change this.\u003C/p>\n\u003Cp>Great. Alright. We'll shrink that to half width. And I'm just gonna duplicate this one, and you can see how easy it is to build this out. Is reimbursable.\u003C/p>\n\u003Cp>Directus makes a a lot of this quick and painless. So we're gonna keep this on the expense collection. Is reimbursable. Yes. This is a reimbursable expense.\u003C/p>\n\u003Cp>We've got the user that created this, but, I wonder if there's a a use case where I have to expenses that somebody else submitted because they didn't do their job. So let's just add a owner, an expender. What is the the proper term for that? Buyer? Let's just call it an owner.\u003C/p>\n\u003Cp>Expense owner makes sense to me. That's what we'll call it for now. That is going to be our directus users collection. So that'll be the users of our app. Great.\u003C/p>\n\u003Cp>That's what we'll call it. That's the hardest thing in development is actually naming things. Okay. So this looks pretty good. This is a good start.\u003C/p>\n\u003Cp>What else are we missing for this? We've got a category, we've got the tags, Name of the tags. Yeah. We could we could do that. Let's let's build in a little more functionality.\u003C/p>\n\u003Cp>So we'll do tags. Maybe I wanna do tags and categories as a separate collection, and actually build those into the relationships. So category to me, there's only one single category that an expense can be in. Tags obviously could be many. So here we are going to use a many to 1.\u003C/p>\n\u003Cp>So this one, expense belongs to a single category. That's what we're gonna call it. Let's call it expense category. Great. And for our related collection, let's also do expense categories as the name of that.\u003C/p>\n\u003Cp>Now, I could click Save and Directus will do some magic for me, or I can open up advanced mode and go in and actually look at the details of this relationship. So if we look, we can see we've got this collection, we've got an expense category field, and Directus is telling me that, hey, we're gonna create a new collection called expense categories and we're gonna use the ID field of that to populate. Alright. The mini to 1, I could control the display template, but for now I don't have any other fields because that, collection is yet to be created. But we'll go in and go ahead and create this field here.\u003C/p>\n\u003Cp>And if I were to go back to my data model as well, we'll see that Directus created that collection for us. So now we've got our expense category. Let's go in and do tags as well. So tags are going to be a many to many relationship inside Directus. One tag could be applied to many different expenses, and many expenses could have many different tags.\u003C/p>\n\u003Cp>Great. You didn't know we were getting into data model expertise today, but let's name this thing. What are we gonna call it? We're just gonna call it tags. Right?\u003C/p>\n\u003Cp>We're going to create a related collection called expense tags and what else do we need to do? Show a link to the item so we can edit that tag as needed. Great. Save. Alright.\u003C/p>\n\u003Cp>So now we've got the basis of our expenses. Let's go in and flesh out these other ones that we just created. For expense categories, we're probably gonna have a name for that expense category. And, you know, maybe we got, like, an account number for our profit and loss, account name. Cool.\u003C/p>\n\u003Cp>Alright. And then for our tags, let's go in and add name and maybe we want to create a color for our tags. So I'm just gonna search for the color field here inside Directus. We'll give each tag a color. I could go through and preset these.\u003C/p>\n\u003Cp>If you leave this blank, Directus will actually create, give you a few of those presets that look nice anyway. Alright. So always gotta do my proper formatting. This is kind of a speed run, but one of the other things that I am a little OCD about, honestly, is cleaning things up and having a nice row of or nice columns of icons inside the app. So you can see a website project I've got working on here, but, you know, for now let's just roll with this.\u003C/p>\n\u003Cp>So we've got our tags, we've got our categories. The last thing that we need to do, if we open up one of our expense reports, which we have no data in that. We need to figure that one out. But we need a link back to our expense reports. So let's go into the data model.\u003C/p>\n\u003Cp>We'll go to expense reports. And this is really nice. We just recently added this search feature inside the data model, which is great, especially when you have hundreds of collections. So we'll go into our expense report. We're going to have a name.\u003C/p>\n\u003Cp>Do we even need a name? Yeah. We'll give it a name of the expense report. This could be a title. Bryant's second quarter expenses, blah blah blah, etcetera.\u003C/p>\n\u003Cp>Alright. Then we have a owner or could be like submitted by, you know, that's probably better but we stuck with owner on the other one so we'll just roll with that. For our related collection, we're gonna do direct us users. And you could pick these from a drop down over here on the right just by clicking those, but if you'll notice when I hit on one of the actual collections inside Directus, this turns purple just to let me know I'm doing the right thing here. So we got the owner of this expense report, yeah, I don't like that terminology, but I I don't wanna backtrack at this point.\u003C/p>\n\u003Cp>Date created. Alright. What else do we have? Then we're gonna need the actual expenses on this. So in this case, we've got a one to many relationship.\u003C/p>\n\u003Cp>So one expense report has many expenses. We're gonna call the key for this expenses, that's what it's gonna be stored as a column in the database as, and then we're going to pick our expenses as the related collection. And for the foreign key, we don't have one that exists already, but Directus will create one for us, which is nice. We're just gonna call it expense report because there is a single report it is linked to. And then I could go in and use the let's change this to a table view.\u003C/p>\n\u003Cp>We could see the merchant. Maybe we want to add the expense date, the amount, and, maybe the category. So here when it's a related collection I can just pick up the the related fields as well. Great, enable searching and filtering, hit save and it's probably good enough for now, right? Let's dive in and actually start creating some expense reports.\u003C/p>\n\u003Cp>So, I've got a new expense report. Brent's 4th quarter, it's not even the 4th quarter, 3rd quarter, let's just call it September expenses. Expenses. Great. Alright.\u003C/p>\n\u003Cp>So we go in, I'm gonna be the owner of this. I am not a user at this point, so I'll just use mister hop along. And now we can go in and add some of our expenses. Alright. So we've got Starbucks.\u003C/p>\n\u003Cp>The expense date is going to be this morning at, what, 7:30. I'm in West Virginia of all places, so we don't have a a ton of coffee options here. I know some people are gonna be mad that we've got Starbucks in here. That was expensive. So let's say 2102 has the amount.\u003C/p>\n\u003Cp>Coffee Needed that real bad. Alright. Is this billable? Probably not. Is this reimbursable?\u003C/p>\n\u003Cp>You know, we could have this where I could select this, but one of the other things you could do inside Directus is preset this to a default setting for, like, the the current user, for example. So for now, I'll just pre we'll pick hops along. We don't have any expense categories. I wanna say this would probably fall under meals and entertainment or just meals. No, you wanna divide those out.\u003C/p>\n\u003Cp>I hate doing bookkeeping, so accounting account number, I wanna say it's like the 3 or 4 100. Mills Entertainment. Great. Cool. Keeping track of all that.\u003C/p>\n\u003Cp>Tags. What are we gonna tag this as? Coffee would be the name of it. I don't really like any of these. Maybe we can pick, yeah let's just go with red for coffee.\u003C/p>\n\u003Cp>That's great. Cool. Alright. So we've got our information here. We've added our first expense.\u003C/p>\n\u003Cp>Pretty great. Cool. We've got our expense report. Yeah. The next thing maybe we want to fire off this expense report to somebody on our team.\u003C/p>\n\u003Cp>How are we doing on time? We're at 43 minutes, so that took about, 18, 20 minutes to flesh out this. How can we send this expense report to a team member, for approval? Well, first of all, let's do an expense report. Let's add an approved by.\u003C/p>\n\u003Cp>So we wanna track who this was approved by. That's gonna be a directus users, so it's gonna be one of our users. Great. And then maybe an approval date, Right. So we'll do a date timestamp, date approved.\u003C/p>\n\u003Cp>Great. No 24 hour format. So now we got 2 fields to track our approvals. Let's go in and use the flows inside Directus to send this report off or send a link to somebody to this report. So send expense report for approval.\u003C/p>\n\u003Cp>Great. And then we can even customize this. Let's do send. Yeah. There we go.\u003C/p>\n\u003Cp>Looks great. Scheduled send. We'll make this orange just so it stands out. Alright, so now I've got a list of different triggers. I want to actually trigger this one manually.\u003C/p>\n\u003Cp>So Directus gives us the option within a specific collection on the layout page or the detail page to send this off. So we will go in and look for expense reports. Okay. There's my expense report. Great.\u003C/p>\n\u003Cp>And then I could choose whether this is asynchronous. So, you know, do I wanna do this in the background? What are the locations where I can trigger this? So we wanna do on the collections and items pages. That's fine.\u003C/p>\n\u003Cp>We'll do both of those. If you are on the collection page, this will require a selection. We we do need you to pick this to actually send this off. And then next I'm gonna do this require confirmation because I get the ability to add different fields to the system, to prop for those fields when I send it. So I am going to add a new field for approver.\u003C/p>\n\u003Cp>So who do we need to send this? Approver. Who needs to approve this report. Great. Now the type I'm gonna pick here is gonna be JSON because we have a special little interface called the collection item drop down where I can pick a value from this and store it in JSON.\u003C/p>\n\u003Cp>So here again, we're gonna do directus users, and I'm gonna pick the user that I want to approve this report. And I could go in and as far as customizing the display template as well. You know, let's do the avatar, first name, last name. Great. And then maybe we even wanna add a note.\u003C/p>\n\u003Cp>Note. Close note to approver and that'll be just a simple string. No big deal. Great. Got the field width, let's make it full width.\u003C/p>\n\u003Cp>Actually, let's do a text area for that. Just a quick note. Great. Okay. So now that we've got that, what I'm gonna do, I'm gonna save this and I just wanna get a look at what it's gonna look like when I'm on my expense report.\u003C/p>\n\u003Cp>So over here on the right hand side, you can see Send Expense Report for Approval. I can select items or if I'm inside this expense report, I can click expense report, and then I get this confirmation dialogue of who needs to approve this, Matt. My note to Matt is going to be, hey, I need more budget for these videos. Great. Run flow on current item and let's just see what that came up with.\u003C/p>\n\u003Cp>So inside my flows, I can go in and check my logs and I could see what was actually sent down the pipe. So here's the trigger, here's the body of the payload, and this is actually what we received. This is what we will use to fire off that message. Alright. So this looks great.\u003C/p>\n\u003Cp>What I'm going to do, I'm just gonna copy this information, this is what I need, and I open up Versus Code. Let's start with a new window here. Where are you? New window. New file.\u003C/p>\n\u003Cp>Alright. So this is our trigger data. Great. Perfect. Okay.\u003C/p>\n\u003Cp>So now let's build the steps of our flow. We wanna send this expense report for approval, but we need an email to send it to, and we do not have that data here. So we need to actually get that data. So that's gonna be our next step. Get approval approver email is what we'll call it.\u003C/p>\n\u003Cp>And we will click read items from the database. The permissions from the trigger should be fine. I can also change this to full access if this is gonna be, triggered from somewhere other than inside the app. And for the collection we are going to use the directus users, but you can see this is not coming up in the search because it's a system collection. So I'm just gonna go in and hit edit raw value and we will do directus underscore users.\u003C/p>\n\u003Cp>That's our collection. Now for the IDs, we are going to use our little squarely bracket mustache syntax and we are gonna pick up the ID from this approver key that we selected. Alright, so, how do I do split screen here? Fancy fancy. Alright.\u003C/p>\n\u003Cp>So this is gonna be located at, so we use our brackets, mustache, we're gonna do dollar sign trigger. Trigger is the only one that has a special syntax. All the other keys of your operations get appended under, the key that you set here without the dollar sign. That's one of the big things that I see a lot of people get tripped up on. And it's gonna be dot body dot approver.key.\u003C/p>\n\u003Cp>Great. That's gonna be our ID. Do we need a query for this? I don't think so because we're picking up the key there. Let's hit save and then next we want to, I'm I'm just gonna save this.\u003C/p>\n\u003Cp>Let's run that one more time. So we'll go back to our expense reports. I'll pick Matt here. Hey, dude. We'll go back to our flow, and this is a good way of building flow so I can actually see the details of what we've got going on.\u003C/p>\n\u003Cp>Now I can see an issue right away. This for this demo anyway. This is gonna send to matt@example.com, which is not a real email address. So I'm just gonna quickly change this to one of my own emails just for demonstration purposes and to prove that we can actually build this app this quickly. Alright, we'll go back to our flows.\u003C/p>\n\u003Cp>Now let's actually send that email, and again when working with flows it's helpful to have like a code editor open, but, this case is fairly straightforward. We are going to send an email. We'll choose the send email operation and for the addresses we're gonna use that squirrely mustache syntax again. This is going to be get, this is where my memory struggles. This is called git approver email.\u003C/p>\n\u003Cp>So the 2 is gonna be git_approveremail, and that's gonna be the user object. So we're just gonna do email. I have to hit enter here to save this. We could also add additional emails if I wanted to if I wanted to cc myself. Let's call this expense reports.\u003C/p>\n\u003Cp>And the type here could be WYSIWYG. You can even choose a template if you are using our custom extensions, but let's go in do this as a WYSIWYG will be fine and we're going to do the trigger. So we we just want to pick up the content from our notes, which is gonna be body dot note. Should be Great. Alright.\u003C/p>\n\u003Cp>So this should populate that message into our WYSIWYG editor when we send this email out. Fingers crossed we're gonna save this. Let's test it out and see. How we doing on time? Alright.\u003C/p>\n\u003Cp>So we got about 32 minutes left, maybe a little less than that because I was, failing on throwing together the or I was failing to do the the countdown correctly. But, let's give this a shot. We'll go in, we'll send the expense report for approval, and we're going to send it to Rabat Miner. I need some more lattes. Alright.\u003C/p>\n\u003Cp>So we fire that off. Let's go back in and check our logs just to make sure. And it looks like the email went out. I'm just gonna open up my email inbox and voila, it it sent this out. That's great, that's all good, but, where's the actual expense report?\u003C/p>\n\u003Cp>There's no link. So let's go back in and what I'm gonna do, I'm just gonna capture the URL for this from my directus admin. Great. And then we're gonna go into our flow. We'll go back to that email and we just wanna give Matt like a button or a link or something here.\u003C/p>\n\u003Cp>So we're gonna do view and approve report. We'll enter in this email and we're gonna use our bracket mustache syntax and we'll do the trigger dot body dot keys 0. So the first item of the keys array is what we want to include. Cool. Great.\u003C/p>\n\u003Cp>Gravy. Save. Save. Alright. So now let's go back in.\u003C/p>\n\u003Cp>We'll send this expense report one last time, one more time with a link, hopefully. Fingers crossed, right? Alright, so now we wait and boom, we have a link. We click on the link, it logs us in and, Matt should be logging in to update the status on this. So we may want to go in and update the statuses for these.\u003C/p>\n\u003Cp>Let's do our expense reports. Let's change the status, draft. We'll do submitted for 1. Submitted. So submitted for approval.\u003C/p>\n\u003Cp>Approved. Maybe not approved is 1. Rejected. Let's call it rejected. That'll that'll be fun.\u003C/p>\n\u003Cp>That's a little harsh for an expense report, like, hey, your expense report was rejected. But, yeah, no worries. Oh, I goofed that up, didn't I? Should've saved before I started slamming away on the keyboard. Submitted.\u003C/p>\n\u003Cp>Alright. We'll just clean this up. Draft. Okay. Approved.\u003C/p>\n\u003Cp>Rejected. Alright. Let's just show the raw value for now or maybe a formatted value. That's fine. We'll do like a border, make it fancy looking, allow other values, no, allow no selection, no, for the interface though maybe it could be interesting if we do radio buttons.\u003C/p>\n\u003Cp>Great. Alright. Cool. So now we go in. Matt can approve this expense report and save it.\u003C/p>\n\u003Cp>But as you can see, I'm logged in as myself. I could potentially approve this expense report myself, which is is not cool. So we could use the roles and permissions inside Directus for that, where I could go in and say a given role like if we just had one called team member for example, I could go in and for our expense reports, I could go in and give custom permissions for this so that I cannot edit a certain field like the status. So this would prevent that user from actually, I'm sorry. I did that backwards.\u003C/p>\n\u003Cp>I would check all the other fields like the that user can update everything else about this expense report except the status or any other items that were that I needed. And likewise, one of the other things that I could do here is when I create a report, if we use the custom permissions for this, you could go in and define the presets where you can say, hey the current user is the default for this particular field. Sounds great. Alright. That is basic functionality of this.\u003C/p>\n\u003Cp>We've got the ability to add expenses, submit expense reports. You know, you might wanna try and tag into some other system to pay out these expenses, but for now I could stroke a check for those, I guess. But that leads us back to, boom boom boom, the automagic receipt management. So this part is a bit scary scary to me. I don't know how we're gonna do this in 26 minutes.\u003C/p>\n\u003Cp>Actually, I'm lying to you. I do know, what we're going to try because I've already cheated a little bit. Let's get that clock out of the way. I'm gonna make this full screen again. I have found this other service called Mindy Mindy?\u003C/p>\n\u003Cp>Mindy? Not sure what it's called, but it takes the receipts OCR, optical character recognition magic on them and apparently spits back out some data. Now, for the sake of time, all I've done is logged into this app and, set up an account. I haven't done anything else with it, so you're almost as fresh as I am at this point. I did see just a quick little tour tooltip that told me this is the API that I want.\u003C/p>\n\u003Cp>So let's take a look at at how we could do this. Anytime I upload an expense that has an image attached to it, and Directus is mobile friendly, so I could potentially log into this from my phone and update these expenses as well, just to to prove it to you there. Anytime I update one of those expenses, it has an image attached to it. We wanna send this off to this service and hopefully, I'd, like, not have to deal with actually entering in all the separate fields. Great.\u003C/p>\n\u003Cp>So first thing we need to do is, we got our expenses, let me close this one. We need to add an image field to upload the expense. So we'll go into our expenses. Let's add a image, receipt image or receipt file or we could just call it file. No need to get fancy with it.\u003C/p>\n\u003Cp>Alright. So now we've got the ability to upload a file. Let's start building a flow and then we'll incorporate this other service. So we've got, automagic COCR. Cool.\u003C/p>\n\u003Cp>Alright. So we'll do that on a vinthook. Let's do a action non blocking, so we don't wanna block the thread. Anytime a new item is created inside expenses, and we'll start there. Right?\u003C/p>\n\u003Cp>Alright. So let's go into, let's actually go back to our expenses. Let's just call this a new expense. Actually, we're not gonna do any of that. All I wanna do here and maybe we move this file up to the start actually.\u003C/p>\n\u003Cp>Let's move our file way up here. Alright. So the ideal scenario here is all I have to do create a new expense, upload an image of that, and then this service and Directus would do the rest for me. So let's just Google receipt images. Let's get a receipt image, see if we can find one that's pretty gnarly as well.\u003C/p>\n\u003Cp>What's this guy? Tesco Metro. Yeah. This one looks pretty beat up. This will be a good test, right?\u003C/p>\n\u003Cp>Save this image. Where did I go? Too many different tabs going on. So here's our Tesco shopping receipt. We hit save.\u003C/p>\n\u003Cp>Merchant value can't be null. That was a problem of mine, maybe I should go in and remove that, but you could set that up where it wasn't required. But now, if we take a look at our flow, that should have triggered our flow, we've got a payload, we can see here's the file that we're going to potentially pick up. Great. One of the other things that I wanna make sure, because we're gonna have to send this file to that service, is I'm gonna check our system collection and make sure that direct us files has public read permissions, and I could drill down to where, you know, only, files within a certain folder are available if I wanted to get very specific for this.\u003C/p>\n\u003Cp>Let's just leave it as is. Again, this is an Inexpanify Tribute, not a clone. Great. Alright, so now we've got our data coming from our new expense, we are going to wanna pass that to the mindd API. So let's see the API here.\u003C/p>\n\u003Cp>Alright. Load some stuff up for me. Alright. If I was smart I would have gone in here and cheated a little bit more than what I'm doing now. Alright.\u003C/p>\n\u003Cp>So I don't have an API key. How do I create an API key? API keys. Alright. We'll create an API key.\u003C/p>\n\u003Cp>We're gonna call this Directus. Great. Copy that API key. Don't you guys steal my API key? So So we'll go back to the API, go back to our documentation.\u003C/p>\n\u003Cp>Here's everything we're gonna need. Now is a good time to do the split screen stuff with, Arc. Great browser, still struggling, like 6 months on to figure this thing out. Alright, give myself a little more room. So we got the post request.\u003C/p>\n\u003Cp>We are going to use the send to, let's just call it OCR receipt. We are going to use the webhook request URL. We're gonna do a post request as it says. Here is our URL that we're gonna pick up. Hopefully, we could just copy that.\u003C/p>\n\u003Cp>Savvy. Of course it takes that post request. This looks okay. We got our headers, we're gonna add authorization. It's gonna be a bearer token.\u003C/p>\n\u003Cp>Raycast, great app as well, by the way. We'll paste in our API key and then the content type. Content type. Multipartformdata. K.\u003C/p>\n\u003Cp>Let's look for, like, a curl example. Document is path to your file. Okay. So it looks like we've got a document key and I'll pick up oh, the other downside of working locally. That might be the death of this particular project.\u003C/p>\n\u003Cp>Right? This is on my local computer. I've got 18 minutes. It's not going to be able to access that image. Let's do some more cheating.\u003C/p>\n\u003Cp>Right? Go back to our receipt images. I got the image address. Don't be mad at me for cheating on this. Alright.\u003C/p>\n\u003Cp>Let's take a look at that. We got a document. Here's the public URL of an image. Document can either be a file object URL or base 64. That should be savvy.\u003C/p>\n\u003Cp>Next time I'll have to do this on a standard cloud instance, not one of my local copies. Alright. So we got this. We're gonna take the webhook request. Let's see if they give us like a response what that response looks like.\u003C/p>\n\u003Cp>Date, confidence, document type, line items, locale, supplier address. I tell you what, let's just send this off and see what we come back with. Alright, so we're just going to pretend and actually this is gonna be correct because it's the same receipt but just a little cheating involved. We'll add a merchant name for this, We've sent this off to our auto magic receipt. It says unauthorized.\u003C/p>\n\u003Cp>Use token. What did I forget? Oh, token. Let's see. I've forgotten something.\u003C/p>\n\u003Cp>API reference authorization token. It's not bearer token. It's just token. Great. Let's go back and try her again.\u003C/p>\n\u003Cp>Keep editing. Don't forget to save your changes, kids. Alright. We'll create a new expense. Use the same receipt we've already uploaded so we don't duplicate it, and I really should remove that requirement.\u003C/p>\n\u003Cp>Go to our flows, See what we got. Bad request. Missing boundary and multipartform data. Alright. How are we gonna structure this, right?\u003C/p>\n\u003Cp>Missing boundary and multi part form data. Do we actually need the content type? Can we try that? See if we can get away with it. Otherwise, I think I'm gonna have to reformat everything.\u003C/p>\n\u003Cp>Alright. So we go to our flows. Let's take a look. Logs. Hey.\u003C/p>\n\u003Cp>There we go. Alright. So we've got our prediction. And we probably don't have to use form data because we're not passing an actual document. Cool.\u003C/p>\n\u003Cp>So we've got our document. This is a giant string, or a giant JSON object. Tons of stuff. Taxes, the value, supplier phone number, prediction, document type, expense receipt. Here's our line items.\u003C/p>\n\u003Cp>We just need our amount, right, and dates. So I'm just gonna copy this whole thing. Let's open up Versus Code. Do this and we'll look for date. Okay.\u003C/p>\n\u003Cp>So there we go. We've got our date. It'd be great if there was a way to copy that path. Right? Let's close, let's separate these, do this, do a little side by side action going with Versus Code.\u003C/p>\n\u003Cp>Alright, so now we need to update this item. How we doing on time? We've got 13 minutes. Let's get it done. So now we're gonna update our expense with the data coming from this API.\u003C/p>\n\u003Cp>And I may wanna do something like this where we say format data or I could just directly update that data. Anyway, so we'll go into update data, let's say update expense. We are going to use the permissions from the trigger, this is gonna be an expense. The IDs are gonna be coming from trigger dot body, dot keys dot 0, I believe. We'll take a guess and the payload that we want, we got the merchants.\u003C/p>\n\u003Cp>We wanna pass that. We want the expense date, I believe. The amount of the expense, and I think that would be a win. So let's look for the merchant, supplier, supplier address, supplier name. Alright.\u003C/p>\n\u003Cp>Let's start with the date. Right? So how do we get a hold of this guy? We've got our data. Alright.\u003C/p>\n\u003Cp>Let's just save this. That is our OCR receipt. That's our key. And of course because this is an invalid payload, it did not save. Alright.\u003C/p>\n\u003Cp>So our date expense date is going to be blah blah blah. We've got, OCR receipt dot data dot documents dot inference. I love that these guys are saying all this, dot pages dot prediction, debugging this is gonna be a nightmare, dot date dot value. That is deeply nested if I've ever seen it. Great.\u003C/p>\n\u003Cp>Alright. So next we've got the merchant. Let's try it. That is gonna be something probably similar, and I already see a typo. Prediction.\u003C/p>\n\u003Cp>Okay. So we'll just copy paste that. Alright. And that's gonna be called what? The supplier supplier name value prediction dot supplier underscore name dot value.\u003C/p>\n\u003Cp>Okay. Great. And then last but not least should be the amount. And we are closing in on 10 minutes on the clock, hopefully this thing works out. Would hate to start off on a loss.\u003C/p>\n\u003Cp>Alright. So the amount let's look for the amount. Total amount is what we're looking for. Point 99 confidence, that's great. That's what we're looking for.\u003C/p>\n\u003Cp>Got a lot of confidence in this. We got the total underscore amount dot value. Alright. So there we go. We've got our JSON.\u003C/p>\n\u003Cp>I'm just gonna copy it just to verify, update. Let's try it one more time. Let's delete these failed attempts. Alright. Tesco shopping receipt.\u003C/p>\n\u003Cp>We're gonna have to enter that in. Hit save and automatic receipt OCR. I don't have permission to access this because I don't have the proper key. So I didn't pass the key. I got the payload wrong as well.\u003C/p>\n\u003Cp>What did we get wrong here? Dot data, OCR receipt dot data dot document. Maybe it's just document. Maybe we can omit data. OCR receipt.\u003C/p>\n\u003Cp>And I also need to fix the keys because I didn't get those correct either. So it's actually just key. That's what's coming from the trigger, the expense. So body trigger dot body dot key. Alright.\u003C/p>\n\u003Cp>Fingers crossed. We'll run this again. This works this time. Let's hope so. Do our nice little t here.\u003C/p>\n\u003Cp>Boom boom boom. You don't have permission to access this. Still says undefined. So we're getting this back from the API. That's great.\u003C/p>\n\u003Cp>Trigger dot body dot key. The key should be correct, though. Payload trigger dot payload oh, no. This is an event hook. I'm sorry.\u003C/p>\n\u003Cp>So I've got it wrong. Should just be trigger dot key. No body. It's fine. Trigger dot key.\u003C/p>\n\u003Cp>That part's good. We are not good here, though. So let's do this. Let's disconnect this. Let's add a step.\u003C/p>\n\u003Cp>You just log this to the console. Log. And we are going to do get OCR receipt is what we're calling it. We're just gonna console log OCR receipt. Log OCR receipt.\u003C/p>\n\u003Cp>See what we're getting past. Alright. We'll drag that down here for now. We are running out of time for like a fun playing beat the clock. We'll hit save this.\u003C/p>\n\u003Cp>Let's go take a look. What do we got for him, Johnny? Log to the console, message dot data. Okay. So do we have to include message dot data?\u003C/p>\n\u003Cp>Worth a shot. Let's see. With 5 minutes and 40 seconds on the clock, Wasting precious time. So we have message dot data Message dot data dot documents. I'll just copy, paste.\u003C/p>\n\u003Cp>Alright. It's either gonna be a big finish or a sad ending. Not sure. We'll try it out and see. Alright.\u003C/p>\n\u003Cp>So we've got that same image, we're running it one more time. Flows do us nicely error update expenses set where invalid input syntax for type numeric. Still not getting the correct stuff coming from the app here. What in the world is going on? Alright.\u003C/p>\n\u003Cp>Let's bring in the big guns here. What are we what am I doing wrong? Logging to the console. Alright. Let's try picking up the data that we want.\u003C/p>\n\u003Cp>Let's go with, run scripts. Where are you? Format, data. Alright. So we're gonna get the data from receipt equals OCR data dot OCR receipt.\u003C/p>\n\u003Cp>Okay. And let's just return receipt for now. Let's string this together. Let's see if I could just get something going here. Format data.\u003C/p>\n\u003Cp>What's going on? It's gonna be a sprint to the finish. To to to log to the console. I can't even get my, so we'll drag that. Let's log into the console.\u003C/p>\n\u003Cp>Let's see what comes out of this. Expense, save, format data, flows, auto magic. Alright. So we got the run script, status. Okay.\u003C/p>\n\u003Cp>So we got data. We wanna pick up the data dot document. Right? I swear that's right. Data.ocrdoc equals receipt dot data dot document.\u003C/p>\n\u003Cp>Let's return the dock. We'll just keep drilling down. We got 2 minutes left. Let's see how this is gonna play out. Save t.\u003C/p>\n\u003Cp>Save it. Flows. If I was smart, I would have had multiple options. Okay. So we got this.\u003C/p>\n\u003Cp>Let's get down to our inferencing. Oh, I see what was wrong now. Duh. Pages is an array. That's where we were going wrong the entire time.\u003C/p>\n\u003Cp>Pages 0. Don't you love that? Message. It's not message. The message is coming from that console log.\u003C/p>\n\u003Cp>Alright. So with 1 minute and 21 seconds on the clock, let's save this and just see what comes up as the clock is ticking down. T. Now, if I hit save and stay it should be doing that for me, but it did not. However, did it?\u003C/p>\n\u003Cp>Did it actually do it? Expenses. There it is. Boom. With 49 seconds on the clock remaining, we have populated the receipt information from a third party API.\u003C/p>\n\u003Cp>Therefore, fulfilling our requirements for this, That is a wrap on the first edition of 100 Apps in 100 Hours. I feel like we need like a little Tiger Woods just, yeah, finish up strong. So if you enjoyed this series, or this episode of this series, stay tuned. We've got more coming like this. Would love for you to hop into our Directus Discord community and send us your feedback on what apps you would like to see us build next with our set of tools that we have here at Directus.\u003C/p>","Hi. Brian here. Welcome to the inaugural episode of 100 apps in 100 hours, where I will take some of your favorite apps and try to recreate them in under 1 hour or die trying. Hopefully not dying, but you get the idea. And first on our list today is Expensify. So we're going to build an Expensify clone. Expensify is expense management. And let's actually I'm not really sure I like the word clone here. Let's call this, a tribute, like the Tenacious d song. This is just a tribute because it would be very difficult to build all the functionality of Expensify in 1 hour. Obviously, they've built an entire business and they probably have thousands of developers, but let's get as close as we can. So when I think about Expensify, let's just go to their website, I think of the the first thing I think of is scanning the receipt, taking a picture of it, and it does the automagic stuff, where it automatically fills out expense reports for me, which, to be honest, is kind of a pain in the rear anyway. So, that is one thing that we're going to be concerned with, the let's just map this out. Automagic Receipt Management. We're going to have expense reports. Expense reports. So we wanna roll up all of those expenses, and, some of those are gonna be billable, And all those may be reimbursable, so we need to track for that. What else? We want to be able to approve expense reports. Seems like a good list for getting started. Alright, so I'm gonna pull up my handy tool of choice, Directus. So Directus, for those of you who aren't familiar with it, is a back end toolkit that allows you to build pretty much anything or everything, and get instant APIs on top of any SQL database. That's the pitch. I swear that's it. Let's actually run the clock, and I'm late on the clock here, but we'll start the clock 60 minutes on the timer. Let's go. We're gonna hop into Directus, and I am in my data model settings right now. And I'll zoom way in so you guys can actually see this alright. First thing we're gonna do is let's spec out our expenses. So let's just dive into our data model. We're gonna have expenses as one of our collections, so Directus is gonna create this table for us. And when we go to the optional system fields, we'll probably have a status. Let's go with the sort for the order on the expense report and we'll just select all these system fields because they have some presets that are useful to us. Alright, so we've got expenses, and I'm just gonna use a Expensify attribute. We'll create a folder here for this. We even give it like a nice green color. Boom. We're gonna organize all that inside our Expensify attribute. Okay. So the next thing, we are going to have an expense report. Right? We've got to roll up all those expenses somewhere. This is where we'll do it. Okay. Great. Okay. What else are we going to need? Let's just get started with this functionality. Alright. So, we go back to Expensify and let's try to figure out exactly what their data model looks like. My favorite way of doing that, short of interacting with the app is searching for their API. So we'll just go to the Expensify API, we'll go to their API reference, we'll just zoom way in on this, Expensify integration server, all you need to integrate. Alright. So request formats, we see what's going on here. Let's look for read and get policy lists. Maybe create. Okay. I see here's the expense creator. Alright. So, okay, it looks like here's a definition of the expense object. So reading through this, we've got a merchant, we've got the date of the expense, we've got the amount incense, we've got the currency, that's an integer, that's probably on purpose, three letter code of the expense, external ID, there's a category, there's a tag, is it billable, is it reimbursable, Comments, reports, IDs, tax. Okay. Straightforward enough, let's dive in. We'll go to our expenses and first, let's add our merchant. So we'll just create a new field inside our data model for Directus. This is just going to be a string type and clear enough, we are going to require value. We've got to have somebody that we got this receipt from. Let's change that to half width. And now let's move on to the amount. So let's do an input field. Now I could do this as an integer in cents. What I'm going to do instead is just use a decimal for this. So we'll do a decimal, I could change the scale to 2 points and, looks good. Alright. So now we've got the amount for the expense, we need to add a date for the expense. Expense date, let's just call that. So we'll choose the date time here and I'm gonna pick the time stamp as the type just because that will respect any time zones. I'm in the US. I use the 24 hour format. Or let's not use the 24 hour format since I'm in the US. No need to include the seconds. That's great. So we got our merchant. We got the amount. We got the expense date. Let's just do the name of the expense. Is that inside Expensify? Let's take a look. Created amounts, optional tag. Yeah. I'm just gonna give a name for the expense. Maybe we have some notes for that. Great. We'll add the text area field for that. We don't need any formatting. Great. Merchant amount, let's do a little cleanup on this. Directus makes it so easy to build apps that look great for your users. Alright. What else do we have? An expense comment. Got that. So then we have things like, is this billable? Is this reimbursable? So these are going to be toggles inside Directus. That's the name of the interface as we call it. Let's do is billable. I like to prefix those with is, just keeps things organized. Default value will be false, and looks good. Maybe we want to embellish this a little bit. So if you go into the advanced mode inside Directus, we can add notes for our users. Is this expense can we invoice this to a client? Great. Alright. Enabled. Let's just call it billable for the label. That seems a little better. The display is where it will show out show up in some of the different layouts inside Directus when you're looking at a list of records. Yeah. I don't see any need to change this. Great. Alright. We'll shrink that to half width. And I'm just gonna duplicate this one, and you can see how easy it is to build this out. Is reimbursable. Directus makes a a lot of this quick and painless. So we're gonna keep this on the expense collection. Is reimbursable. Yes. This is a reimbursable expense. We've got the user that created this, but, I wonder if there's a a use case where I have to expenses that somebody else submitted because they didn't do their job. So let's just add a owner, an expender. What is the the proper term for that? Buyer? Let's just call it an owner. Expense owner makes sense to me. That's what we'll call it for now. That is going to be our directus users collection. So that'll be the users of our app. Great. That's what we'll call it. That's the hardest thing in development is actually naming things. Okay. So this looks pretty good. This is a good start. What else are we missing for this? We've got a category, we've got the tags, Name of the tags. Yeah. We could we could do that. Let's let's build in a little more functionality. So we'll do tags. Maybe I wanna do tags and categories as a separate collection, and actually build those into the relationships. So category to me, there's only one single category that an expense can be in. Tags obviously could be many. So here we are going to use a many to 1. So this one, expense belongs to a single category. That's what we're gonna call it. Let's call it expense category. Great. And for our related collection, let's also do expense categories as the name of that. Now, I could click Save and Directus will do some magic for me, or I can open up advanced mode and go in and actually look at the details of this relationship. So if we look, we can see we've got this collection, we've got an expense category field, and Directus is telling me that, hey, we're gonna create a new collection called expense categories and we're gonna use the ID field of that to populate. Alright. The mini to 1, I could control the display template, but for now I don't have any other fields because that, collection is yet to be created. But we'll go in and go ahead and create this field here. And if I were to go back to my data model as well, we'll see that Directus created that collection for us. So now we've got our expense category. Let's go in and do tags as well. So tags are going to be a many to many relationship inside Directus. One tag could be applied to many different expenses, and many expenses could have many different tags. Great. You didn't know we were getting into data model expertise today, but let's name this thing. What are we gonna call it? We're just gonna call it tags. Right? We're going to create a related collection called expense tags and what else do we need to do? Show a link to the item so we can edit that tag as needed. Great. Save. Alright. So now we've got the basis of our expenses. Let's go in and flesh out these other ones that we just created. For expense categories, we're probably gonna have a name for that expense category. And, you know, maybe we got, like, an account number for our profit and loss, account name. Cool. Alright. And then for our tags, let's go in and add name and maybe we want to create a color for our tags. So I'm just gonna search for the color field here inside Directus. We'll give each tag a color. I could go through and preset these. If you leave this blank, Directus will actually create, give you a few of those presets that look nice anyway. Alright. So always gotta do my proper formatting. This is kind of a speed run, but one of the other things that I am a little OCD about, honestly, is cleaning things up and having a nice row of or nice columns of icons inside the app. So you can see a website project I've got working on here, but, you know, for now let's just roll with this. So we've got our tags, we've got our categories. The last thing that we need to do, if we open up one of our expense reports, which we have no data in that. We need to figure that one out. But we need a link back to our expense reports. So let's go into the data model. We'll go to expense reports. And this is really nice. We just recently added this search feature inside the data model, which is great, especially when you have hundreds of collections. So we'll go into our expense report. We're going to have a name. Do we even need a name? Yeah. We'll give it a name of the expense report. This could be a title. Bryant's second quarter expenses, blah blah blah, etcetera. Alright. Then we have a owner or could be like submitted by, you know, that's probably better but we stuck with owner on the other one so we'll just roll with that. For our related collection, we're gonna do direct us users. And you could pick these from a drop down over here on the right just by clicking those, but if you'll notice when I hit on one of the actual collections inside Directus, this turns purple just to let me know I'm doing the right thing here. So we got the owner of this expense report, yeah, I don't like that terminology, but I I don't wanna backtrack at this point. Date created. Alright. What else do we have? Then we're gonna need the actual expenses on this. So in this case, we've got a one to many relationship. So one expense report has many expenses. We're gonna call the key for this expenses, that's what it's gonna be stored as a column in the database as, and then we're going to pick our expenses as the related collection. And for the foreign key, we don't have one that exists already, but Directus will create one for us, which is nice. We're just gonna call it expense report because there is a single report it is linked to. And then I could go in and use the let's change this to a table view. We could see the merchant. Maybe we want to add the expense date, the amount, and, maybe the category. So here when it's a related collection I can just pick up the the related fields as well. Great, enable searching and filtering, hit save and it's probably good enough for now, right? Let's dive in and actually start creating some expense reports. So, I've got a new expense report. Brent's 4th quarter, it's not even the 4th quarter, 3rd quarter, let's just call it September expenses. Expenses. Great. Alright. So we go in, I'm gonna be the owner of this. I am not a user at this point, so I'll just use mister hop along. And now we can go in and add some of our expenses. Alright. So we've got Starbucks. The expense date is going to be this morning at, what, 7:30. I'm in West Virginia of all places, so we don't have a a ton of coffee options here. I know some people are gonna be mad that we've got Starbucks in here. That was expensive. So let's say 2102 has the amount. Coffee Needed that real bad. Alright. Is this billable? Probably not. Is this reimbursable? You know, we could have this where I could select this, but one of the other things you could do inside Directus is preset this to a default setting for, like, the the current user, for example. So for now, I'll just pre we'll pick hops along. We don't have any expense categories. I wanna say this would probably fall under meals and entertainment or just meals. No, you wanna divide those out. I hate doing bookkeeping, so accounting account number, I wanna say it's like the 3 or 4 100. Mills Entertainment. Great. Cool. Keeping track of all that. Tags. What are we gonna tag this as? Coffee would be the name of it. I don't really like any of these. Maybe we can pick, yeah let's just go with red for coffee. That's great. Cool. Alright. So we've got our information here. We've added our first expense. Pretty great. Cool. We've got our expense report. Yeah. The next thing maybe we want to fire off this expense report to somebody on our team. How are we doing on time? We're at 43 minutes, so that took about, 18, 20 minutes to flesh out this. How can we send this expense report to a team member, for approval? Well, first of all, let's do an expense report. Let's add an approved by. So we wanna track who this was approved by. That's gonna be a directus users, so it's gonna be one of our users. Great. And then maybe an approval date, Right. So we'll do a date timestamp, date approved. Great. No 24 hour format. So now we got 2 fields to track our approvals. Let's go in and use the flows inside Directus to send this report off or send a link to somebody to this report. So send expense report for approval. Great. And then we can even customize this. Let's do send. Yeah. There we go. Looks great. Scheduled send. We'll make this orange just so it stands out. Alright, so now I've got a list of different triggers. I want to actually trigger this one manually. So Directus gives us the option within a specific collection on the layout page or the detail page to send this off. So we will go in and look for expense reports. Okay. There's my expense report. Great. And then I could choose whether this is asynchronous. So, you know, do I wanna do this in the background? What are the locations where I can trigger this? So we wanna do on the collections and items pages. That's fine. We'll do both of those. If you are on the collection page, this will require a selection. We we do need you to pick this to actually send this off. And then next I'm gonna do this require confirmation because I get the ability to add different fields to the system, to prop for those fields when I send it. So I am going to add a new field for approver. So who do we need to send this? Approver. Who needs to approve this report. Great. Now the type I'm gonna pick here is gonna be JSON because we have a special little interface called the collection item drop down where I can pick a value from this and store it in JSON. So here again, we're gonna do directus users, and I'm gonna pick the user that I want to approve this report. And I could go in and as far as customizing the display template as well. You know, let's do the avatar, first name, last name. Great. And then maybe we even wanna add a note. Note. Close note to approver and that'll be just a simple string. No big deal. Great. Got the field width, let's make it full width. Actually, let's do a text area for that. Just a quick note. Great. Okay. So now that we've got that, what I'm gonna do, I'm gonna save this and I just wanna get a look at what it's gonna look like when I'm on my expense report. So over here on the right hand side, you can see Send Expense Report for Approval. I can select items or if I'm inside this expense report, I can click expense report, and then I get this confirmation dialogue of who needs to approve this, Matt. My note to Matt is going to be, hey, I need more budget for these videos. Great. Run flow on current item and let's just see what that came up with. So inside my flows, I can go in and check my logs and I could see what was actually sent down the pipe. So here's the trigger, here's the body of the payload, and this is actually what we received. This is what we will use to fire off that message. Alright. So this looks great. What I'm going to do, I'm just gonna copy this information, this is what I need, and I open up Versus Code. Let's start with a new window here. Where are you? New window. New file. Alright. So this is our trigger data. Great. Perfect. Okay. So now let's build the steps of our flow. We wanna send this expense report for approval, but we need an email to send it to, and we do not have that data here. So we need to actually get that data. So that's gonna be our next step. Get approval approver email is what we'll call it. And we will click read items from the database. The permissions from the trigger should be fine. I can also change this to full access if this is gonna be, triggered from somewhere other than inside the app. And for the collection we are going to use the directus users, but you can see this is not coming up in the search because it's a system collection. So I'm just gonna go in and hit edit raw value and we will do directus underscore users. That's our collection. Now for the IDs, we are going to use our little squarely bracket mustache syntax and we are gonna pick up the ID from this approver key that we selected. Alright, so, how do I do split screen here? Fancy fancy. Alright. So this is gonna be located at, so we use our brackets, mustache, we're gonna do dollar sign trigger. Trigger is the only one that has a special syntax. All the other keys of your operations get appended under, the key that you set here without the dollar sign. That's one of the big things that I see a lot of people get tripped up on. And it's gonna be dot body dot approver.key. Great. That's gonna be our ID. Do we need a query for this? I don't think so because we're picking up the key there. Let's hit save and then next we want to, I'm I'm just gonna save this. Let's run that one more time. So we'll go back to our expense reports. I'll pick Matt here. Hey, dude. We'll go back to our flow, and this is a good way of building flow so I can actually see the details of what we've got going on. Now I can see an issue right away. This for this demo anyway. This is gonna send to matt@example.com, which is not a real email address. So I'm just gonna quickly change this to one of my own emails just for demonstration purposes and to prove that we can actually build this app this quickly. Alright, we'll go back to our flows. Now let's actually send that email, and again when working with flows it's helpful to have like a code editor open, but, this case is fairly straightforward. We are going to send an email. We'll choose the send email operation and for the addresses we're gonna use that squirrely mustache syntax again. This is going to be get, this is where my memory struggles. This is called git approver email. So the 2 is gonna be git_approveremail, and that's gonna be the user object. So we're just gonna do email. I have to hit enter here to save this. We could also add additional emails if I wanted to if I wanted to cc myself. Let's call this expense reports. And the type here could be WYSIWYG. You can even choose a template if you are using our custom extensions, but let's go in do this as a WYSIWYG will be fine and we're going to do the trigger. So we we just want to pick up the content from our notes, which is gonna be body dot note. Should be Great. Alright. So this should populate that message into our WYSIWYG editor when we send this email out. Fingers crossed we're gonna save this. Let's test it out and see. How we doing on time? Alright. So we got about 32 minutes left, maybe a little less than that because I was, failing on throwing together the or I was failing to do the the countdown correctly. But, let's give this a shot. We'll go in, we'll send the expense report for approval, and we're going to send it to Rabat Miner. I need some more lattes. Alright. So we fire that off. Let's go back in and check our logs just to make sure. And it looks like the email went out. I'm just gonna open up my email inbox and voila, it it sent this out. That's great, that's all good, but, where's the actual expense report? There's no link. So let's go back in and what I'm gonna do, I'm just gonna capture the URL for this from my directus admin. Great. And then we're gonna go into our flow. We'll go back to that email and we just wanna give Matt like a button or a link or something here. So we're gonna do view and approve report. We'll enter in this email and we're gonna use our bracket mustache syntax and we'll do the trigger dot body dot keys 0. So the first item of the keys array is what we want to include. Cool. Great. Gravy. Save. Save. Alright. So now let's go back in. We'll send this expense report one last time, one more time with a link, hopefully. Fingers crossed, right? Alright, so now we wait and boom, we have a link. We click on the link, it logs us in and, Matt should be logging in to update the status on this. So we may want to go in and update the statuses for these. Let's do our expense reports. Let's change the status, draft. We'll do submitted for 1. Submitted. So submitted for approval. Approved. Maybe not approved is 1. Rejected. Let's call it rejected. That'll that'll be fun. That's a little harsh for an expense report, like, hey, your expense report was rejected. But, yeah, no worries. Oh, I goofed that up, didn't I? Should've saved before I started slamming away on the keyboard. Submitted. Alright. We'll just clean this up. Draft. Okay. Approved. Rejected. Alright. Let's just show the raw value for now or maybe a formatted value. That's fine. We'll do like a border, make it fancy looking, allow other values, no, allow no selection, no, for the interface though maybe it could be interesting if we do radio buttons. Great. Alright. Cool. So now we go in. Matt can approve this expense report and save it. But as you can see, I'm logged in as myself. I could potentially approve this expense report myself, which is is not cool. So we could use the roles and permissions inside Directus for that, where I could go in and say a given role like if we just had one called team member for example, I could go in and for our expense reports, I could go in and give custom permissions for this so that I cannot edit a certain field like the status. So this would prevent that user from actually, I'm sorry. I did that backwards. I would check all the other fields like the that user can update everything else about this expense report except the status or any other items that were that I needed. And likewise, one of the other things that I could do here is when I create a report, if we use the custom permissions for this, you could go in and define the presets where you can say, hey the current user is the default for this particular field. Sounds great. Alright. That is basic functionality of this. We've got the ability to add expenses, submit expense reports. You know, you might wanna try and tag into some other system to pay out these expenses, but for now I could stroke a check for those, I guess. But that leads us back to, boom boom boom, the automagic receipt management. So this part is a bit scary scary to me. I don't know how we're gonna do this in 26 minutes. Actually, I'm lying to you. I do know, what we're going to try because I've already cheated a little bit. Let's get that clock out of the way. I'm gonna make this full screen again. I have found this other service called Mindy Mindy? Mindy? Not sure what it's called, but it takes the receipts OCR, optical character recognition magic on them and apparently spits back out some data. Now, for the sake of time, all I've done is logged into this app and, set up an account. I haven't done anything else with it, so you're almost as fresh as I am at this point. I did see just a quick little tour tooltip that told me this is the API that I want. So let's take a look at at how we could do this. Anytime I upload an expense that has an image attached to it, and Directus is mobile friendly, so I could potentially log into this from my phone and update these expenses as well, just to to prove it to you there. Anytime I update one of those expenses, it has an image attached to it. We wanna send this off to this service and hopefully, I'd, like, not have to deal with actually entering in all the separate fields. Great. So first thing we need to do is, we got our expenses, let me close this one. We need to add an image field to upload the expense. So we'll go into our expenses. Let's add a image, receipt image or receipt file or we could just call it file. No need to get fancy with it. Alright. So now we've got the ability to upload a file. Let's start building a flow and then we'll incorporate this other service. So we've got, automagic COCR. Cool. Alright. So we'll do that on a vinthook. Let's do a action non blocking, so we don't wanna block the thread. Anytime a new item is created inside expenses, and we'll start there. Right? Alright. So let's go into, let's actually go back to our expenses. Let's just call this a new expense. Actually, we're not gonna do any of that. All I wanna do here and maybe we move this file up to the start actually. Let's move our file way up here. Alright. So the ideal scenario here is all I have to do create a new expense, upload an image of that, and then this service and Directus would do the rest for me. So let's just Google receipt images. Let's get a receipt image, see if we can find one that's pretty gnarly as well. What's this guy? Tesco Metro. Yeah. This one looks pretty beat up. This will be a good test, right? Save this image. Where did I go? Too many different tabs going on. So here's our Tesco shopping receipt. We hit save. Merchant value can't be null. That was a problem of mine, maybe I should go in and remove that, but you could set that up where it wasn't required. But now, if we take a look at our flow, that should have triggered our flow, we've got a payload, we can see here's the file that we're going to potentially pick up. Great. One of the other things that I wanna make sure, because we're gonna have to send this file to that service, is I'm gonna check our system collection and make sure that direct us files has public read permissions, and I could drill down to where, you know, only, files within a certain folder are available if I wanted to get very specific for this. Let's just leave it as is. Again, this is an Inexpanify Tribute, not a clone. Great. Alright, so now we've got our data coming from our new expense, we are going to wanna pass that to the mindd API. So let's see the API here. Alright. Load some stuff up for me. Alright. If I was smart I would have gone in here and cheated a little bit more than what I'm doing now. Alright. So I don't have an API key. How do I create an API key? API keys. Alright. We'll create an API key. We're gonna call this Directus. Great. Copy that API key. Don't you guys steal my API key? So So we'll go back to the API, go back to our documentation. Here's everything we're gonna need. Now is a good time to do the split screen stuff with, Arc. Great browser, still struggling, like 6 months on to figure this thing out. Alright, give myself a little more room. So we got the post request. We are going to use the send to, let's just call it OCR receipt. We are going to use the webhook request URL. We're gonna do a post request as it says. Here is our URL that we're gonna pick up. Hopefully, we could just copy that. Savvy. Of course it takes that post request. This looks okay. We got our headers, we're gonna add authorization. It's gonna be a bearer token. Raycast, great app as well, by the way. We'll paste in our API key and then the content type. Content type. Multipartformdata. K. Let's look for, like, a curl example. Document is path to your file. Okay. So it looks like we've got a document key and I'll pick up oh, the other downside of working locally. That might be the death of this particular project. Right? This is on my local computer. I've got 18 minutes. It's not going to be able to access that image. Let's do some more cheating. Right? Go back to our receipt images. I got the image address. Don't be mad at me for cheating on this. Alright. Let's take a look at that. We got a document. Here's the public URL of an image. Document can either be a file object URL or base 64. That should be savvy. Next time I'll have to do this on a standard cloud instance, not one of my local copies. Alright. So we got this. We're gonna take the webhook request. Let's see if they give us like a response what that response looks like. Date, confidence, document type, line items, locale, supplier address. I tell you what, let's just send this off and see what we come back with. Alright, so we're just going to pretend and actually this is gonna be correct because it's the same receipt but just a little cheating involved. We'll add a merchant name for this, We've sent this off to our auto magic receipt. It says unauthorized. Use token. What did I forget? Oh, token. Let's see. I've forgotten something. API reference authorization token. It's not bearer token. It's just token. Great. Let's go back and try her again. Keep editing. Don't forget to save your changes, kids. Alright. We'll create a new expense. Use the same receipt we've already uploaded so we don't duplicate it, and I really should remove that requirement. Go to our flows, See what we got. Bad request. Missing boundary and multipartform data. Alright. How are we gonna structure this, right? Missing boundary and multi part form data. Do we actually need the content type? Can we try that? See if we can get away with it. Otherwise, I think I'm gonna have to reformat everything. Alright. So we go to our flows. Let's take a look. Logs. Hey. There we go. Alright. So we've got our prediction. And we probably don't have to use form data because we're not passing an actual document. Cool. So we've got our document. This is a giant string, or a giant JSON object. Tons of stuff. Taxes, the value, supplier phone number, prediction, document type, expense receipt. Here's our line items. We just need our amount, right, and dates. So I'm just gonna copy this whole thing. Let's open up Versus Code. Do this and we'll look for date. Okay. So there we go. We've got our date. It'd be great if there was a way to copy that path. Right? Let's close, let's separate these, do this, do a little side by side action going with Versus Code. Alright, so now we need to update this item. How we doing on time? We've got 13 minutes. Let's get it done. So now we're gonna update our expense with the data coming from this API. And I may wanna do something like this where we say format data or I could just directly update that data. Anyway, so we'll go into update data, let's say update expense. We are going to use the permissions from the trigger, this is gonna be an expense. The IDs are gonna be coming from trigger dot body, dot keys dot 0, I believe. We'll take a guess and the payload that we want, we got the merchants. We wanna pass that. We want the expense date, I believe. The amount of the expense, and I think that would be a win. So let's look for the merchant, supplier, supplier address, supplier name. Alright. Let's start with the date. Right? So how do we get a hold of this guy? We've got our data. Alright. Let's just save this. That is our OCR receipt. That's our key. And of course because this is an invalid payload, it did not save. Alright. So our date expense date is going to be blah blah blah. We've got, OCR receipt dot data dot documents dot inference. I love that these guys are saying all this, dot pages dot prediction, debugging this is gonna be a nightmare, dot date dot value. That is deeply nested if I've ever seen it. Great. Alright. So next we've got the merchant. Let's try it. That is gonna be something probably similar, and I already see a typo. Prediction. Okay. So we'll just copy paste that. Alright. And that's gonna be called what? The supplier supplier name value prediction dot supplier underscore name dot value. Okay. Great. And then last but not least should be the amount. And we are closing in on 10 minutes on the clock, hopefully this thing works out. Would hate to start off on a loss. Alright. So the amount let's look for the amount. Total amount is what we're looking for. Point 99 confidence, that's great. That's what we're looking for. Got a lot of confidence in this. We got the total underscore amount dot value. Alright. So there we go. We've got our JSON. I'm just gonna copy it just to verify, update. Let's try it one more time. Let's delete these failed attempts. Alright. Tesco shopping receipt. We're gonna have to enter that in. Hit save and automatic receipt OCR. I don't have permission to access this because I don't have the proper key. So I didn't pass the key. I got the payload wrong as well. What did we get wrong here? Dot data, OCR receipt dot data dot document. Maybe it's just document. Maybe we can omit data. OCR receipt. And I also need to fix the keys because I didn't get those correct either. So it's actually just key. That's what's coming from the trigger, the expense. So body trigger dot body dot key. Alright. Fingers crossed. We'll run this again. This works this time. Let's hope so. Do our nice little t here. Boom boom boom. You don't have permission to access this. Still says undefined. So we're getting this back from the API. That's great. Trigger dot body dot key. The key should be correct, though. Payload trigger dot payload oh, no. This is an event hook. I'm sorry. So I've got it wrong. Should just be trigger dot key. No body. It's fine. Trigger dot key. That part's good. We are not good here, though. So let's do this. Let's disconnect this. Let's add a step. You just log this to the console. Log. And we are going to do get OCR receipt is what we're calling it. We're just gonna console log OCR receipt. Log OCR receipt. See what we're getting past. Alright. We'll drag that down here for now. We are running out of time for like a fun playing beat the clock. We'll hit save this. Let's go take a look. What do we got for him, Johnny? Log to the console, message dot data. Okay. So do we have to include message dot data? Worth a shot. Let's see. With 5 minutes and 40 seconds on the clock, Wasting precious time. So we have message dot data Message dot data dot documents. I'll just copy, paste. Alright. It's either gonna be a big finish or a sad ending. Not sure. We'll try it out and see. Alright. So we've got that same image, we're running it one more time. Flows do us nicely error update expenses set where invalid input syntax for type numeric. Still not getting the correct stuff coming from the app here. What in the world is going on? Alright. Let's bring in the big guns here. What are we what am I doing wrong? Logging to the console. Alright. Let's try picking up the data that we want. Let's go with, run scripts. Where are you? Format, data. Alright. So we're gonna get the data from receipt equals OCR data dot OCR receipt. Okay. And let's just return receipt for now. Let's string this together. Let's see if I could just get something going here. Format data. What's going on? It's gonna be a sprint to the finish. To to to log to the console. I can't even get my, so we'll drag that. Let's log into the console. Let's see what comes out of this. Expense, save, format data, flows, auto magic. Alright. So we got the run script, status. Okay. So we got data. We wanna pick up the data dot document. Right? I swear that's right. Data.ocrdoc equals receipt dot data dot document. Let's return the dock. We'll just keep drilling down. We got 2 minutes left. Let's see how this is gonna play out. Save t. Save it. Flows. If I was smart, I would have had multiple options. Okay. So we got this. Let's get down to our inferencing. Oh, I see what was wrong now. Duh. Pages is an array. That's where we were going wrong the entire time. Pages 0. Don't you love that? Message. It's not message. The message is coming from that console log. Alright. So with 1 minute and 21 seconds on the clock, let's save this and just see what comes up as the clock is ticking down. T. Now, if I hit save and stay it should be doing that for me, but it did not. However, did it? Did it actually do it? Expenses. There it is. Boom. With 49 seconds on the clock remaining, we have populated the receipt information from a third party API. Therefore, fulfilling our requirements for this, That is a wrap on the first edition of 100 Apps in 100 Hours. I feel like we need like a little Tiger Woods just, yeah, finish up strong. So if you enjoyed this series, or this episode of this series, stay tuned. We've got more coming like this. Would love for you to hop into our Directus Discord community and send us your feedback on what apps you would like to see us build next with our set of tools that we have here at Directus.","published",[143],{"people_id":144},{"id":145,"first_name":146,"last_name":147,"avatar":148,"bio":149,"links":150},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[151,153],{"url":135,"service":152},"website",{"service":154,"url":155},"github","https://github.com/bryantgillespie",[157,169,176],{"id":158,"type":159,"title":8,"description":8,"url":8,"image":8,"recommended_episode_id":160},"3452bafa-9f28-43f2-9e14-3c5de754cb7e","episode",{"id":161,"title":162,"slug":163,"tile":164,"description":165,"season":166},"30c48566-52cd-4728-a633-ca9675acc959","Mission: Airbnb","airbnb","58537616-bf5c-4251-ae41-115fae3d13df","Can Bryant follow AirBnb's trajectory from scrappy startup to short term rental royalty – and build an AirBnb clone in one hour or less? Follow along as he attempts to builds a complete backend and frontend to manage rental listings, bookings, hosts, and more.",{"show":167},{"slug":168},"100-apps-100-hours",{"id":170,"type":171,"title":172,"description":173,"url":174,"image":175,"recommended_episode_id":8},"039ef759-0fc3-4584-b7a4-a5205d3a21a8","url","MightyHQ Delivers Speed, Simplicity and Cost Savings with Custom Ecommerce Tooling","From idea to MVP in hours. Learn how two entrepreneurs built, launched, and continuously iterate on an all-in-one ecommerce platform from the ground-up. ","https://directus.io/case-studies/mightyhq","04ba70bc-b674-4028-bbb5-5782469098ef",{"id":177,"type":159,"title":8,"description":8,"url":8,"image":8,"recommended_episode_id":178},"992f81a1-0a4a-4fa8-8dee-10b4e3d0b848",{"id":179,"title":180,"slug":181,"tile":182,"description":183,"season":184},"a311b57d-34e7-4073-9cf3-6a8c6c0f8b85","Mission: Netflix Clone","netflix","012d9dd3-99b5-40ee-8811-9928bdc0a5bc","It's Bryant vs the streaming giant – Netflix – in this exciting episode. He has just one hour to build an app that replicates the core functionalities of the video streaming service.",{"show":185},{"slug":168},{"id":187,"number":136,"year":188,"episodes":189,"show":198},"56dda5ff-2c3a-41ce-ae3a-580d6101026b","2023",[122,190,191,192,193,194,195,179,196,161,197],"8434838a-8e4f-489a-8da2-fbf10de5de6a","c997b25e-400c-4350-bba4-f63853d844f7","1109be0d-8ab5-479b-a052-8ad30d9ffb1c","dcb952e0-7d13-43ea-9b1c-f2ca63efd07d","0aae287d-3916-4d91-9310-25828998e562","8fed0eee-43f6-4767-8b77-79da7a059821","9271a4fc-addf-4400-9591-f2f2ec07bd79","c067c012-5fe1-4d70-8946-8bfc8e1b22c0",{"title":199,"tile":200},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":202,"meta_description":125},"Building an Expensify Clone",{"id":204,"slug":205,"season":206,"vimeo_id":207,"description":208,"tile":209,"length":210,"resources":8,"people":8,"episode_number":136,"published":211,"title":212,"video_transcript_html":213,"video_transcript_text":214,"content":8,"seo":215,"status":141,"episode_people":216,"recommendations":218},"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","crm","14fda5f2-95de-4dbe-a4e2-3fd956c21c19","936325383","In this intense one-hour challenge, watch as Bryant incredibly builds a full-featured custom CRM from the ground up using Directus. He builds contacts, organizations, deal pipelines, activities, and more.","f6b880a0-5cd2-45ad-beff-3117f0a78581",56,"2024-04-19","Mission: Customer Relationship Manager","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season.\u003C/p>\u003Cp>If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality.\u003C/p>\u003Cp>That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM.\u003C/p>\u003Cp>A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go.\u003C/p>\u003Cp>So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to.\u003C/p>\u003Cp>What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up.\u003C/p>\u003Cp>So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people.\u003C/p>\u003Cp>I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities.\u003C/p>\u003Cp>Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality.\u003C/p>\u003Cp>Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection.\u003C/p>\u003Cp>Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right?\u003C/p>\u003Cp>When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by.\u003C/p>\u003Cp>That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright.\u003C/p>\u003Cp>So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time.\u003C/p>\u003Cp>And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with.\u003C/p>\u003Cp>Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation.\u003C/p>\u003Cp>So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields.\u003C/p>\u003Cp>Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title.\u003C/p>\u003Cp>And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string.\u003C/p>\u003Cp>Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great.\u003C/p>\u003Cp>K. Phone, email. We'll put email above phone. Cool. Looks nice.\u003C/p>\u003Cp>Alright. Let's just take a look. Right? We've got first name. Go ahead.\u003C/p>\u003Cp>Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact.\u003C/p>\u003Cp>These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations.\u003C/p>\u003Cp>So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great.\u003C/p>\u003Cp>Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right?\u003C/p>\u003Cp>Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization?\u003C/p>\u003Cp>You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company.\u003C/p>\u003Cp>So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save.\u003C/p>\u003Cp>So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that?\u003C/p>\u003Cp>Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop.\u003C/p>\u003Cp>So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship.\u003C/p>\u003Cp>And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that.\u003C/p>\u003Cp>So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations.\u003C/p>\u003Cp>Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right?\u003C/p>\u003Cp>On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great.\u003C/p>\u003Cp>So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here.\u003C/p>\u003Cp>Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes.\u003C/p>\u003Cp>I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right?\u003C/p>\u003Cp>So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection.\u003C/p>\u003Cp>Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright.\u003C/p>\u003Cp>So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay.\u003C/p>\u003Cp>So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league.\u003C/p>\u003Cp>We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay.\u003C/p>\u003Cp>So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great.\u003C/p>\u003Cp>Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this.\u003C/p>\u003Cp>So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at.\u003C/p>\u003Cp>Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great.\u003C/p>\u003Cp>Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data.\u003C/p>\u003Cp>So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right?\u003C/p>\u003Cp>We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields.\u003C/p>\u003Cp>But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal.\u003C/p>\u003Cp>So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here.\u003C/p>\u003Cp>We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact.\u003C/p>\u003Cp>The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes.\u003C/p>\u003Cp>We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially.\u003C/p>\u003Cp>Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right?\u003C/p>\u003Cp>Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection.\u003C/p>\u003Cp>Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it.\u003C/p>\u003Cp>And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages.\u003C/p>\u003Cp>Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application.\u003C/p>\u003Cp>So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right?\u003C/p>\u003Cp>Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red.\u003C/p>\u003Cp>This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted?\u003C/p>\u003Cp>Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that.\u003C/p>\u003Cp>Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo.\u003C/p>\u003Cp>Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got?\u003C/p>\u003Cp>Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray.\u003C/p>\u003Cp>Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here.\u003C/p>\u003Cp>So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great.\u003C/p>\u003Cp>If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value.\u003C/p>\u003Cp>So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like.\u003C/p>\u003Cp>Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right?\u003C/p>\u003Cp>So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on.\u003C/p>\u003Cp>I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright.\u003C/p>\u003Cp>Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need.\u003C/p>\u003Cp>Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage.\u003C/p>\u003Cp>That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there?\u003C/p>\u003Cp>1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact.\u003C/p>\u003Cp>Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh.\u003C/p>\u003Cp>Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale.\u003C/p>\u003Cp>Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on.\u003C/p>\u003Cp>Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay.\u003C/p>\u003Cp>Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display.\u003C/p>\u003Cp>So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back.\u003C/p>\u003Cp>We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail.\u003C/p>\u003Cp>That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL.\u003C/p>\u003Cp>Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool.\u003C/p>\u003Cp>And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout.\u003C/p>\u003Cp>Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created.\u003C/p>\u003Cp>I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here?\u003C/p>\u003Cp>Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right?\u003C/p>\u003Cp>So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great.\u003C/p>\u003Cp>And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense.\u003C/p>\u003Cp>We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone.\u003C/p>\u003Cp>One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call.\u003C/p>\u003Cp>The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way.\u003C/p>\u003Cp>Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting.\u003C/p>\u003Cp>And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright.\u003C/p>\u003Cp>So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email?\u003C/p>\u003Cp>Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great.\u003C/p>\u003Cp>Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good.\u003C/p>\u003Cp>Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived.\u003C/p>\u003Cp>What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right?\u003C/p>\u003Cp>Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed.\u003C/p>\u003Cp>Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date.\u003C/p>\u003Cp>Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right?\u003C/p>\u003Cp>So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal.\u003C/p>\u003Cp>Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright.\u003C/p>\u003Cp>So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities.\u003C/p>\u003Cp>We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type.\u003C/p>\u003Cp>Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment.\u003C/p>\u003Cp>But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to.\u003C/p>\u003Cp>Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner.\u003C/p>\u003Cp>Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well.\u003C/p>\u003Cp>Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay.\u003C/p>\u003Cp>Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright.\u003C/p>\u003Cp>Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now.\u003C/p>\u003Cp>I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar.\u003C/p>\u003Cp>Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah.\u003C/p>\u003Cp>This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that.\u003C/p>\u003Cp>And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good.\u003C/p>\u003Cp>Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that.\u003C/p>\u003Cp>And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright.\u003C/p>\u003Cp>Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr.\u003C/p>\u003Cp>Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that.\u003C/p>\u003Cp>And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool.\u003C/p>\u003Cp>Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right?\u003C/p>\u003Cp>If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see.\u003C/p>\u003Cp>Business. Is there a business? There you go. That looks somewhat like a business. We've got activities.\u003C/p>\u003Cp>Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great.\u003C/p>\u003Cp>And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here.\u003C/p>\u003Cp>That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings.\u003C/p>\u003Cp>So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome.\u003C/p>\u003Cp>Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks.\u003C/p>\u003Cp>We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man.\u003C/p>\u003Cp>And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is.\u003C/p>\u003Cp>It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed.\u003C/p>\u003Cp>That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win.\u003C/p>\u003Cp>I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations.\u003C/p>\u003Cp>We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that.\u003C/p>\u003Cp>Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows.\u003C/p>\u003Cp>This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow.\u003C/p>\u003Cp>We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow.\u003C/p>\u003Cp>In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create.\u003C/p>\u003Cp>Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage.\u003C/p>\u003Cp>We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great.\u003C/p>\u003Cp>If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally.\u003C/p>\u003Cp>I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user.\u003C/p>\u003Cp>We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there.\u003C/p>\u003Cp>We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox.\u003C/p>\u003Cp>We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here.\u003C/p>\u003Cp>For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload.\u003C/p>\u003Cp>Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user.\u003C/p>\u003Cp>Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be.\u003C/p>\u003Cp>We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you.\u003C/p>\u003Cp>And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it.\u003C/p>\u003Cp>And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications.\u003C/p>\u003Cp>Send notification. Cool. Send notification. The find_user.id. Cool.\u003C/p>\u003Cp>Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no.\u003C/p>\u003Cp>Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there.\u003C/p>\u003Cp>That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us.\u003C/p>\u003Cp>Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage.\u003C/p>\u003Cp>Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal.\u003C/p>\u003Cp>It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens.\u003C/p>\u003Cp>Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name.\u003C/p>\u003Cp>Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great.\u003C/p>\u003Cp>So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome.\u003C/p>\u003Cp>Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways.\u003C/p>\u003Cp>Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win.\u003C/p>\u003Cp>That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season. If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality. That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM. A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go. So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to. What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up. So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people. I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities. Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality. Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection. Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right? When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by. That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright. So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time. And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with. Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation. So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields. Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title. And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string. Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great. K. Phone, email. We'll put email above phone. Cool. Looks nice. Alright. Let's just take a look. Right? We've got first name. Go ahead. Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact. These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations. So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great. Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right? Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization? You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company. So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save. So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that? Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop. So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship. And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that. So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations. Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right? On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great. So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here. Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes. I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right? So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection. Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright. So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay. So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league. We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay. So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great. Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this. So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at. Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great. Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data. So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right? We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields. But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal. So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here. We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact. The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes. We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially. Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right? Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection. Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it. And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages. Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application. So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right? Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red. This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted? Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that. Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo. Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got? Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray. Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here. So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great. If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value. So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like. Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right? So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on. I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright. Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need. Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage. That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there? 1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact. Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh. Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale. Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on. Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay. Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display. So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back. We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail. That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL. Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool. And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout. Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created. I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here? Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right? So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great. And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense. We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone. One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call. The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way. Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting. And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright. So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email? Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great. Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good. Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived. What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right? Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed. Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date. Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right? So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal. Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright. So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities. We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type. Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment. But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to. Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner. Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well. Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay. Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright. Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now. I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar. Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah. This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that. And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good. Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that. And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright. Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr. Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that. And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool. Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right? If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see. Business. Is there a business? There you go. That looks somewhat like a business. We've got activities. Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great. And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here. That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings. So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome. Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks. We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man. And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is. It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed. That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win. I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations. We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that. Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows. This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow. We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow. In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create. Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage. We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great. If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally. I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user. We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there. We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox. We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here. For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload. Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user. Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be. We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you. And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it. And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications. Send notification. Cool. Send notification. The find_user.id. Cool. Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no. Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there. That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us. Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage. Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal. It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens. Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name. Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great. So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome. Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways. Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win. That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.","592c22ad-1cb2-4d65-9bd7-e0d7c98fe4f2",[217],"e9e66fa8-0650-4e37-ae8b-74755fdd5dca",[],{"reps":220},[221,277],{"name":222,"sdr":8,"link":223,"countries":224,"states":226},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[225],"United States",[227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276],"Michigan","Indiana","Ohio","West Virginia","Kentucky","Virginia","Tennessee","North Carolina","South Carolina","Georgia","Florida","Alabama","Mississippi","New York","MI","IN","OH","WV","KY","VA","TN","NC","SC","GA","FL","AL","MS","NY","Connecticut","CT","Delaware","DE","Maine","ME","Maryland","MD","Massachusetts","MA","New Hampshire","NH","New Jersey","NJ","Pennsylvania","PA","Rhode Island","RI","Vermont","VT","Washington DC","DC",{"name":278,"link":279,"countries":280},"Michelle Riber","https://meetings.hubspot.com/mriber",[281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,258,469,470],"Albania","ALB","Algeria","DZA","Andorra","AND","Angola","AGO","Austria","AUT","Belgium","BEL","Benin","BEN","Bosnia and Herzegovina","BIH","Botswana","BWA","Bulgaria","BGR","Burkina Faso","BFA","Burundi","BDI","Cameroon","CMR","Cape Verde","CPV","Central African Republic","CAF","Chad","TCD","Comoros","COM","Côte d'Ivoire","CIV","Croatia","HRV","Czech Republic","CZE","Democratic Republic of Congo","COD","Denmark","DNK","Djibouti","DJI","Egypt","EGY","Equatorial Guinea","GNQ","Eritrea","ERI","Estonia","EST","Eswatini","SWZ","Ethiopia","ETH","Finland","FIN","France","FRA","Gabon","GAB","Gambia","GMB","Ghana","GHA","Greece","GRC","Guinea","GIN","Guinea-Bissau","GNB","Hungary","HUN","Iceland","ISL","Ireland","IRL","Italy","ITA","Kenya","KEN","Latvia","LVA","Lesotho","LSO","Liberia","LBR","Libya","LBY","Liechtenstein","LIE","Lithuania","LTU","Luxembourg","LUX","Madagascar","MDG","Malawi","MWI","Mali","MLI","Malta","MLT","Mauritania","MRT","Mauritius","MUS","Moldova","MDA","Monaco","MCO","Montenegro","MNE","Morocco","MAR","Mozambique","MOZ","Namibia","NAM","Niger","NER","Nigeria","NGA","North Macedonia","MKD","Norway","NOR","Poland","POL","Portugal","PRT","Republic of Congo","COG","Romania","ROU","Rwanda","RWA","San Marino","SMR","São Tomé and Príncipe","STP","Senegal","SEN","Serbia","SRB","Seychelles","SYC","Sierra Leone","SLE","Slovakia","SVK","Slovenia","SVN","Somalia","SOM","South Africa","ZAF","South Sudan","SSD","Spain","ESP","Sudan","SDN","Sweden","SWE","Tanzania","TZA","Togo","TGO","Tunisia","TUN","Uganda","UGA","United Kingdom","GBR","Vatican City","VAT","Zambia","ZMB","Zimbabwe","ZWE","UK","Germany","Netherlands","Switzerland","CH","NL",1773850421123]