Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature custom columns #218

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b2f1256
Add Reports Page
ShoyebWritesCode Aug 7, 2024
9f3d5ac
Add Migration for Products
ShoyebWritesCode Aug 7, 2024
f04b473
Add Products
ShoyebWritesCode Aug 7, 2024
1721993
Add Search & Edit Product
ShoyebWritesCode Aug 7, 2024
df69579
Add Migration for Invoice
ShoyebWritesCode Aug 7, 2024
eec492d
Add Create Invoice
ShoyebWritesCode Aug 7, 2024
c845b95
Add Save Invoice with Pdf
ShoyebWritesCode Aug 7, 2024
3fbd253
Add Random Invoice Number
ShoyebWritesCode Aug 7, 2024
018ca55
Add Migration for Invoice Items
ShoyebWritesCode Aug 8, 2024
f1b3b9e
Add View invoice
ShoyebWritesCode Aug 8, 2024
5ef44d2
Add Pdf Generation
ShoyebWritesCode Aug 8, 2024
4525e38
Fix Orgazization Logic
ShoyebWritesCode Aug 8, 2024
e8ee589
Fix Invoice Number
ShoyebWritesCode Aug 8, 2024
f551911
Add Download from Index Page
ShoyebWritesCode Aug 8, 2024
f05df25
Fix Invoice Column
ShoyebWritesCode Aug 12, 2024
0e884ff
Add Visibility Checkbox for Columns
ShoyebWritesCode Aug 12, 2024
29d4583
Add Column Visibility Based on Selection
ShoyebWritesCode Aug 12, 2024
61e1adf
Add Migration for Setting
ShoyebWritesCode Aug 12, 2024
3fccd84
Add Apply Column Visibility Filters
ShoyebWritesCode Aug 12, 2024
3b04353
Add Default Columns
ShoyebWritesCode Aug 12, 2024
7a802bb
Add Re-Position Columns
ShoyebWritesCode Aug 12, 2024
59ed6a6
Add Fixed Table Header & Scrollbar
ShoyebWritesCode Aug 12, 2024
248d2e7
Remove id From DefaultProps
ShoyebWritesCode Aug 12, 2024
aa0878a
Fix Spacing
ShoyebWritesCode Aug 14, 2024
5d9dae1
Add Csv Input
ShoyebWritesCode Aug 14, 2024
dd2d479
Add Show Csv Headers in Popup
ShoyebWritesCode Aug 14, 2024
c132a15
Add Map CSV Headers & DB Columns with CSV-Data
ShoyebWritesCode Aug 14, 2024
98f34af
Add Show Preview for Data to be Inserted
ShoyebWritesCode Aug 14, 2024
828ed87
Add Upto 100 Rows in Preview & Back Button
ShoyebWritesCode Aug 15, 2024
fe4d074
Add Import Csv Data to Database
ShoyebWritesCode Aug 15, 2024
5e00772
Fix File Reload Issue
ShoyebWritesCode Aug 15, 2024
af43ff2
Fix Preview Table
ShoyebWritesCode Aug 15, 2024
c516bcf
Fix Csv Preview & Insertion
ShoyebWritesCode Aug 15, 2024
e0f9596
Fix PopUp Navigation
ShoyebWritesCode Aug 15, 2024
50d061a
Add Export As CSV
ShoyebWritesCode Aug 15, 2024
5d08294
Add Organization Dropdown in Contacts CSV Preview
ShoyebWritesCode Aug 16, 2024
6320948
Add Import Contacts
ShoyebWritesCode Aug 19, 2024
0f57585
Add Migrations for New Contact Columns
ShoyebWritesCode Aug 19, 2024
16ed659
Add New Columns
ShoyebWritesCode Aug 19, 2024
57d7f5c
Add Read & Update Custom Columns Data
ShoyebWritesCode Aug 19, 2024
3b38f85
Add View Additional Data on Table
ShoyebWritesCode Aug 19, 2024
8e427ef
Add Events to Trigger Contacts Update
ShoyebWritesCode Aug 20, 2024
de82144
Add Prevent Duplicate Column
ShoyebWritesCode Aug 20, 2024
a3072b5
Remove Dead Code
ShoyebWritesCode Aug 20, 2024
96fed99
Add Fixed Maximum Height for Preview Table
ShoyebWritesCode Aug 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/Events/CustomColumnsUpdated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\Contact;

class CustomColumnsUpdated
{
use SerializesModels;

public $contact;
public $columns;

public function __construct(Contact $contact, array $columns)
{
$this->contact = $contact;
$this->columns = $columns;
}
}
80 changes: 78 additions & 2 deletions app/Http/Controllers/ContactsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@
namespace App\Http\Controllers;

use App\Models\Contact;
use App\Models\Organization;
use App\Models\ContactCustomColumns;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Request;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
use Inertia\Response;
use App\Events\CustomColumnsUpdated;

class ContactsController extends Controller
{
public function index(): Response
{
return Inertia::render('Contacts/Index', [
'filters' => Request::all('search', 'trashed'),
'organizations' => Organization::all(),
'additionalColumns' => Auth::user()->account->contactCustomColumns()->get(),
'contacts' => Auth::user()->account->contacts()
->with('organization')
->orderByName()
->filter(Request::only('search', 'trashed'))
->paginate(10)
->withQueryString()
->through(fn ($contact) => [
->through(fn($contact) => [
'id' => $contact->id,
'name' => $contact->name,
'phone' => $contact->phone,
'city' => $contact->city,
'deleted_at' => $contact->deleted_at,
'organization' => $contact->organization ? $contact->organization->only('name') : null,
'additional_data' => $contact->additional_data,
]),
]);
}
Expand Down Expand Up @@ -90,6 +96,9 @@ public function edit(Contact $contact): Response
->get()
->map
->only('id', 'name'),

'customColumns' => Auth::user()->account->contactCustomColumns()->get(),
'customData' => $contact->contactsCustomData()->get()->map->only('column_id', 'value'),
]);
}

Expand All @@ -101,7 +110,7 @@ public function update(Contact $contact): RedirectResponse
'last_name' => ['required', 'max:50'],
'organization_id' => [
'nullable',
Rule::exists('organizations', 'id')->where(fn ($query) => $query->where('account_id', Auth::user()->account_id)),
Rule::exists('organizations', 'id')->where(fn($query) => $query->where('account_id', Auth::user()->account_id)),
],
'email' => ['nullable', 'max:50', 'email'],
'phone' => ['nullable', 'max:50'],
Expand Down Expand Up @@ -129,4 +138,71 @@ public function restore(Contact $contact): RedirectResponse

return Redirect::back()->with('success', 'Contact restored.');
}

public function importCsv(): RedirectResponse
{
$data = Request::input('data');

$filteredData = array_filter($data, function ($row) {
if (empty($row['name'])) {
return false;
}

foreach ($row as $key => $value) {
if ($key !== 'name' && ($value === null || $value === 'N/A')) {
$row[$key] = null;
}
}

return true;
});

foreach ($filteredData as $row) {
auth()->user()->account->contacts()->create([
'first_name' => explode(' ', $row['name'])[0],
'last_name' => explode(' ', $row['name'])[1],
'phone' => $row['phone'] ?? null,
'city' => $row['city'] ?? null,
'organization_id' => $row['organization_id'] ?? null,

]);
}

return Redirect::route('contacts')->with('success', 'Contacts imported.');
}

public function addColumn()
{
$column = Request::validate([
'name' => ['required', 'max:50'],
'type' => ['required', 'in:string,number,date'],
]);

if (Auth::user()->account->contactCustomColumns()->where('name', $column['name'])->exists()) {
return Redirect::back()->with('error', 'Column already exists.');
} else {

Auth::user()->account->contactCustomColumns()->create($column);

return Redirect::back()->with('success', 'Column added.');
}
}

public function updateCustomColumns(Contact $contact): RedirectResponse
{
$columns = Request::input('columns');

foreach ($columns as $column) {
if (isset($column['value']) && $column['value'] !== null) {
$contact->contactsCustomData()->updateOrCreate(
['column_id' => $column['id']],
['value' => $column['value']]
);
}
}

event(new CustomColumnsUpdated($contact, $columns));

return Redirect::back()->with('success', 'Contact updated.');
}
}
118 changes: 118 additions & 0 deletions app/Http/Controllers/InvoicesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Invoice;
use App\Models\Contact;
use App\Models\InvoiceItem;
use App\Models\Organization;
use App\Models\Product;
use Illuminate\Database\Eloquent\Casts\Json;
use Illuminate\Http\JsonResponse;
use Inertia\Inertia;
use Inertia\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View;

class InvoicesController extends Controller
{
public function index(Request $request)
{
$query = Invoice::query();

if ($request->has('search') && $search = $request->input('search')) {
$query->where('number', 'like', "%{$search}%");
}

$invoices = $query->paginate(10);

$invoices->appends(['search' => $request->input('search')]);

return Inertia::render('Invoices/Index', [
'invoices' => $invoices,
]);
}

public function create(): Response
{
return Inertia::render('Invoices/Create', [
'organizations' => Auth::user()->account->organizations()
->has('contacts')
->get()
->map
->only('id', 'name'),
'products' => Product::all(['id', 'name', 'price', 'quantity']),
]);
}

public function getContacts($organizationId)
{
$contacts = Contact::where('organization_id', $organizationId)->get(['id', 'first_name', 'last_name', 'phone', 'email']);
Log::info($contacts);
return response()->json($contacts);
}

public function store(Request $request)
{
$request->validate([
'number' => 'required',
'amount' => 'required',
'organization_id' => 'required|exists:organizations,id',
'contact_id' => 'required|exists:contacts,id',
'added_products' => 'required|array',
'added_products.*.id' => 'required|exists:products,id',
'added_products.*.quantity' => 'required|integer|min:1',
]);

$organization = Organization::findOrFail($request->organization_id);
$contact = Contact::findOrFail($request->contact_id);

$invoice = Invoice::create([
'number' => $request->number,
'amount' => $request->amount,
'organization_id' => $request->organization_id,
'contact_id' => $request->contact_id,
'organization_name' => $organization->name,
'contact_first_name' => $contact->first_name,
'contact_last_name' => $contact->last_name,
]);

foreach ($request->added_products as $productData) {
$product = Product::findOrFail($productData['id']);
$product->decrement('quantity', $productData['quantity0']);

$item = InvoiceItem::create([
'invoice_id' => $invoice->id,
'name' => $product->name,
'price' => $product->price,
'quantity' => $productData['quantity0'],
]);
}

return redirect()->route('invoices')->with('success', 'Invoice created successfully.');
}

public function view(Invoice $invoice): Response
{
$invoice = Invoice::with('items')->findOrFail($invoice->id);

Check failure on line 99 in app/Http/Controllers/InvoicesController.php

View workflow job for this annotation

GitHub Actions / tests / Static Analysis

Relation 'items' is not found in App\Models\Invoice model.
$contact = Contact::findOrFail($invoice->contact_id);
Log::info($contact);
return Inertia::render('Invoices/View', [
'invoice' => $invoice,
'contact' => $contact,
]);
}

public function download(Invoice $invoice)
{
$localinvoice = Invoice::with('items')->findOrFail($invoice->id);

Check failure on line 110 in app/Http/Controllers/InvoicesController.php

View workflow job for this annotation

GitHub Actions / tests / Static Analysis

Relation 'items' is not found in App\Models\Invoice model.
$localcontact = Contact::findOrFail($invoice->contact_id);

return response()->json([
'localinvoice' => $localinvoice,
'localcontact' => $localcontact,
]);
}
}
79 changes: 71 additions & 8 deletions app/Http/Controllers/OrganizationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,49 @@
namespace App\Http\Controllers;

use App\Models\Organization;
use App\Models\Settings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Request;
use Inertia\Inertia;
use Inertia\Response;
use Illuminate\Http\Request as HttpRequest;

class OrganizationsController extends Controller
{
public function index(): Response
{
$userId = Auth::user()->id;
$path = "org/column/{$userId}";
$settings = Settings::where('path', $path)->first();
$visibleColumns = $settings ? json_decode($settings->value, true) : [];

$allColumns = Organization::allColumns();

$columnsToSelect = array_intersect($allColumns, $visibleColumns);

if (!in_array('id', $columnsToSelect)) {
$columnsToSelect[] = 'id';
}

if (empty($visibleColumns) || empty($columnsToSelect)) {

Check failure on line 32 in app/Http/Controllers/OrganizationsController.php

View workflow job for this annotation

GitHub Actions / tests / Static Analysis

Variable $columnsToSelect in empty() always exists and is not falsy.
$columnsToSelect = Organization::defaultColumns();
}

return Inertia::render('Organizations/Index', [
'visibleColumns' => $visibleColumns,
'filters' => Request::all('search', 'trashed'),
'organizations' => Auth::user()->account->organizations()
->select($columnsToSelect)
->orderBy('name')
->filter(Request::only('search', 'trashed'))
->paginate(10)
->paginate(25)
->withQueryString()
->through(fn ($organization) => [
'id' => $organization->id,
'name' => $organization->name,
'phone' => $organization->phone,
'city' => $organization->city,
'deleted_at' => $organization->deleted_at,
]),
->through(
fn($organization) =>
$organization->only($columnsToSelect)
),
]);
}

Expand Down Expand Up @@ -104,4 +122,49 @@

return Redirect::back()->with('success', 'Organization restored.');
}

public function saveColumnVisibility(HttpRequest $request): RedirectResponse
{
$userId = Auth::user()->id;
$path = "org/column/{$userId}";

$settings = Settings::updateOrCreate(
['path' => $path],
['value' => json_encode($request->input('columns'))]
);

return Redirect::back();
}

public function importCsv(): RedirectResponse
{
$data = Request::input('data');

$filteredData = array_filter($data, function ($row) {
if (empty($row['name'])) {
return false;
}
foreach ($row as $key => $value) {
if ($key !== 'name' && ($value === null || $value === 'N/A')) {
$row[$key] = null;
}
}
return true;
});

foreach ($filteredData as $row) {
auth()->user()->account->organizations()->create([
'name' => $row['name'],
'email' => $row['email'] ?? null,
'phone' => $row['phone'] ?? null,
'address' => $row['address'] ?? null,
'city' => $row['city'] ?? null,
'region' => $row['region'] ?? null,
'country' => $row['country'] ?? null,
'postal_code' => $row['postal_code'] ?? null,
]);
}

return Redirect::route('organizations')->with('success', 'Organizations imported.');
}
}
Loading
Loading