Real World Cases: CRM System in Laravel in ~10 Minutes
Welcome to the Real World Cases series where we are crafting custom applications from real world cases using Laravel framework.
In this example, we are going to build a simple CRM system using Craftable PRO. The prerequisite is that:
- you have installed an empty Laravel project
- you have installed Craftable PRO
- you are logged into Craftable PRO admin interface
For easier walkthrough, this article is also captured as a video screen recording that is available at the very bottom of this article.
Database structure
When building any web app, it’s always good to start thinking about the data entities that our app will work with. Let's assume our CRM system will have Contacts, Companies, Opportunities, and Activities. Contacts will represent people that can be connected to Companies. This simple example will omit the usual Leads table, instead, there will be a status for opportunity named "Lead".
After some deeper thinking about the attributes for each table, we create several migration files that might look like this (feel free to adjust to your needs):
// create_companies_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCompaniesTable extends Migration
{
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address')->nullable();
$table->string('industry')->nullable();
$table->string('website')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('companies');
}
}
// create_contacts_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCompaniesTable extends Migration
{
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address')->nullable();
$table->string('industry')->nullable();
$table->string('website')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('companies');
}
}
// create_opportunities_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCompaniesTable extends Migration
{
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address')->nullable();
$table->string('industry')->nullable();
$table->string('website')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('companies');
}
}
// create_activities_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCompaniesTable extends Migration
{
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address')->nullable();
$table->string('industry')->nullable();
$table->string('website')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('companies');
}
}
Let’s not forget to actually apply the migrations:
php artisan migrate
Generating the CRUD
Perfect. Now when our database structure is ready we can start building the CRUD for our tables.
Companies
Let's begin by creating the CRUD functionalities for the companies entity. To initiate this, we'll use the following command:
php artisan craftable-pro:generate-crud companies --listing-columns=id,name,industry --sortable-columns=id,name --media-collections=logo --image-collections=logo
This command utilizes specific options to tailor our CRUD interface. By using --listing-columns=id,name,industry we specify that our listing page will display the company's ID, name, and industry. With the --sortable-columns=id,name option we designate these columns as sortable. The --media-collections option is included to enable media uploads. Additionally, the --image-collections option is used to specifically limit the uploaded media to images only.
For those who prefer a more guided approach, the wizard mode is an excellent alternative. It offers an interactive process, leading you step-by-step through the CRUD generation. Activate it using:
php artisan craftable-pro:generate-crud companies -w
For a detailed understanding of these command options and their functionalities, please refer to our documentation.
Contacts
Let's proceed to the 'contacts' table, where the same principles apply. Notice the additional option --add-relation, which establishes the relationship between Contacts and Companies. This enables Craftable PRO to generate a select box with the appropriate options and display the correct company name in the listing based on the name attribute.
php artisan craftable-pro:generate-crud contacts --listing-columns=id,first_name,last_name,email,phone,company_id --sortable-columns=id,first_name,last_name --add-relation="belongsTo(Company->name,company_id,id)" --media-collections=image --image-collections=image
Opportunities
Now let’s add the opportunities CRUD.
php artisan craftable-pro:generate-crud opportunities --listing-columns=id,name,company_id,value,status,estimated_close_date --sortable-columns=id,name,status --add-relation="belongsTo(Company->name,company_id,id)"
Activities
And finally, the activities CRUD.
php artisan craftable-pro:generate-crud activities --listing-columns=id,type,subject,date,contact_id,opportunity_id --sortable-columns=id,date,type --add-relation="belongsTo(Contact->email,contact_id,id)" --add-relation="belongsTo(Opportunity->name,opportunity_id,id)" --media-collections=attachments
Building assets
In order to actually test our changes we need to build the frontend assets of the Craftable PRO, so we need to run:
npm run craftable-pro:build
And that’s it. It is that simple!
Customization
Our CRM system has one small flaw. Wouldn’t it be better if users had the possibility to select an opportunity status from a dropdown box instead of manually typing it into a text input?
Fortunately, since all the code is generated, making changes is quite simple. All you need to do is go to resources/js/craftable-pro/Pages/Opportunity/Form.vue and replace the TextInput component:
<TextInput v-model="form.status" name="status" :label="$t('craftable-pro', 'Status')" />
For Multiselect component:
<Multiselect
v-model="form.status"
name="status"
:label="$t('craftable-pro', 'Status')"
:options="[
{value: 'lead', label: $t('craftable-pro', 'Lead')},
{value: 'open', label: $t('craftable-pro', 'Open')},
{value: 'won', label: $t('craftable-pro', 'Won')},
{value: 'lost', label: $t('craftable-pro', 'Lost')},
]"
mode="single"
/>
This is an example of how easily you can modify generated code. Craftable Pro comes with a wide array of prepared components that you can freely use for customizing your admin panel to ensure it meets your needs. Thanks to the code generation principle of Craftable PRO, you have endless possibilities for customization.
See it in Action (video)
We have captured the entire process in a video. At the end, you'll be able to see the fully functioning admin panel in action.
The method for integrating the multiselect feature is demonstrated in the following video:
As you can see, it's possible to create a functional CRM system with a custom database structure under 10 minutes. Moreover, Craftable PRO offers complete flexibility; your hands aren't tied to a predefined system structure. You have the freedom to modify everything directly in the code. So go ahead and create your own CRM system or any other type of admin panel!