First Commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Middleware;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
class AffiliateAdminMiddleware
|
||||
{
|
||||
public function handle(Request $request, callable $next): Response
|
||||
{
|
||||
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Super Admin', 'Admin'])) {
|
||||
return new RedirectResponse('/login');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Providers;
|
||||
|
||||
use Kernel\Application;
|
||||
use Modules\AffiliatesAdmin\Middleware\AffiliateAdminMiddleware;
|
||||
|
||||
class AffiliatesAdminServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
// The ModuleLoader handles loading routes, views, and translations
|
||||
// based on module.json. No need to call them here.
|
||||
}
|
||||
|
||||
public function boot(Application $app): void
|
||||
{
|
||||
$app->addProtectedMiddleware(new AffiliateAdminMiddleware());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Controllers;
|
||||
|
||||
use App\Controllers\BaseAdminController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Modules\AffiliatesAdmin\Models\Affiliate;
|
||||
use Modules\AffiliatesAdmin\Models\AffiliateCommission;
|
||||
use Modules\AffiliatesAdmin\Models\AffiliateBanner;
|
||||
use Modules\AffiliatesAdmin\Models\AffiliateClick;
|
||||
use Modules\Auth\Models\User;
|
||||
|
||||
class AffiliatesAdminController extends BaseAdminController
|
||||
{
|
||||
/**
|
||||
* Check if user has permission.
|
||||
*
|
||||
* @param string $permission
|
||||
* @return void
|
||||
*/
|
||||
protected function checkPermission(string $permission)
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (!$user || !$user->hasPermission($permission)) {
|
||||
$this->addFlash('error', 'Você não tem permissão para acessar esta área.');
|
||||
$this->redirect('admin/dashboard')->send();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the affiliates.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.view');
|
||||
$affiliates = Affiliate::all();
|
||||
return $this->renderAdminView('@affiliatesadmin/index.twig', ['affiliates' => $affiliates]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new affiliate.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage');
|
||||
return $this->renderAdminView('@affiliatesadmin/form.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created affiliate in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage');
|
||||
$data = $request->request->all();
|
||||
|
||||
if (empty($data['affiliates_name']) || empty($data['affiliates_email'])) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_name_email_required'));
|
||||
return $this->redirect('/admin/affiliatesadmin/create');
|
||||
}
|
||||
|
||||
if (!filter_var($data['affiliates_email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_invalid_email'));
|
||||
return $this->redirect('/admin/affiliatesadmin/create');
|
||||
}
|
||||
|
||||
if (Affiliate::where('affiliates_email', $data['affiliates_email'])->exists()) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_email_exists'));
|
||||
return $this->redirect('/admin/affiliatesadmin/create');
|
||||
}
|
||||
|
||||
// Check if user already exists in the system
|
||||
$existingUser = User::where('users_email', $data['affiliates_email'])->first();
|
||||
|
||||
if (!$existingUser && empty($data['password'])) {
|
||||
$this->addFlash('error', 'A senha é obrigatória para criar um novo usuário no sistema.');
|
||||
return $this->redirect('/admin/affiliatesadmin/create');
|
||||
}
|
||||
|
||||
// Create the Affiliate record
|
||||
Affiliate::create($data);
|
||||
|
||||
if ($existingUser) {
|
||||
// Only change role if user is a standard 'user'
|
||||
// Prevent downgrading admins or super_admins
|
||||
if (in_array($existingUser->users_role, ['user', ''])) {
|
||||
$existingUser->users_role = 'member';
|
||||
$existingUser->save();
|
||||
$this->addFlash('success', __('affiliatesadmin.affiliate_created_successfully') . ' O usuário existente foi promovido para "member".');
|
||||
} else {
|
||||
$this->addFlash('success', __('affiliatesadmin.affiliate_created_successfully') . ' O usuário existente manteve seu nível de acesso privilegiado: ' . $existingUser->users_role);
|
||||
}
|
||||
} else {
|
||||
// Create a new System User
|
||||
$user = new User();
|
||||
$user->users_name = $data['affiliates_name'];
|
||||
$user->users_email = $data['affiliates_email'];
|
||||
$user->users_password = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
$user->users_role = 'member';
|
||||
$user->users_status = 'active';
|
||||
$user->save();
|
||||
$this->addFlash('success', __('affiliatesadmin.affiliate_created_successfully') . ' Um novo usuário com nível "member" foi criado.');
|
||||
}
|
||||
|
||||
return $this->redirect('/admin/affiliatesadmin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified affiliate.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.view');
|
||||
$affiliate = Affiliate::findOrFail($id);
|
||||
return $this->renderAdminView('@affiliatesadmin/show.twig', ['affiliate' => $affiliate]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified affiliate.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage');
|
||||
$affiliate = Affiliate::findOrFail($id);
|
||||
return $this->renderAdminView('@affiliatesadmin/form.twig', ['affiliate' => $affiliate]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified affiliate in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage');
|
||||
$affiliate = Affiliate::findOrFail($id);
|
||||
$data = $request->request->all();
|
||||
|
||||
if (empty($data['affiliates_name']) || empty($data['affiliates_email'])) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_name_email_required'));
|
||||
return $this->redirect('/admin/affiliatesadmin/' . $id . '/edit');
|
||||
}
|
||||
|
||||
if (!filter_var($data['affiliates_email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_invalid_email'));
|
||||
return $this->redirect('/admin/affiliatesadmin/' . $id . '/edit');
|
||||
}
|
||||
|
||||
if (Affiliate::where('affiliates_email', $data['affiliates_email'])->where('affiliates_id', '!=', $id)->exists()) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_email_exists'));
|
||||
return $this->redirect('/admin/affiliatesadmin/' . $id . '/edit');
|
||||
}
|
||||
|
||||
$affiliate->update($data);
|
||||
$this->addFlash('success', __('affiliatesadmin.affiliate_updated_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified affiliate from storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function destroy(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage');
|
||||
Affiliate::destroy($id);
|
||||
$this->addFlash('success', __('affiliatesadmin.affiliate_deleted_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of commissions.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function commissions(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.view_commissions');
|
||||
$commissions = AffiliateCommission::all();
|
||||
return $this->renderAdminView('@affiliatesadmin/commissions.twig', ['commissions' => $commissions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve a commission.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function approveCommission(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage_commissions');
|
||||
$commission = AffiliateCommission::findOrFail($id);
|
||||
$commission->affiliate_commissions_status = 'approved';
|
||||
$commission->save();
|
||||
$this->addFlash('success', __('affiliatesadmin.commission_approved_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin/commissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a commission.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function rejectCommission(Request $request, int $id): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage_commissions');
|
||||
$commission = AffiliateCommission::findOrFail($id);
|
||||
$commission->affiliate_commissions_status = 'rejected';
|
||||
$commission->save();
|
||||
$this->addFlash('success', __('affiliatesadmin.commission_rejected_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin/commissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of banners.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function banners(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.view_banners');
|
||||
$banners = AffiliateBanner::all();
|
||||
return $this->renderAdminView('@affiliatesadmin/banners.twig', ['banners' => $banners]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new banner.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function createBanner(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage_banners');
|
||||
return $this->renderAdminView('@affiliatesadmin/banner-form.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created banner in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function storeBanner(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.manage_banners');
|
||||
$data = $request->request->all();
|
||||
|
||||
if (empty($data['affiliate_banners_name']) || empty($data['affiliate_banners_target_url'])) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_banner_fields_required'));
|
||||
return $this->redirect('/admin/affiliatesadmin/banners/create');
|
||||
}
|
||||
|
||||
if ($request->files->has('banner_file')) {
|
||||
$file = $request->files->get('banner_file');
|
||||
if ($file && $file->isValid()) {
|
||||
$data['affiliate_banners_image_url'] = $this->handleBannerUpload($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($data['affiliate_banners_image_url'])) {
|
||||
$this->addFlash('error', 'O arquivo de imagem do banner é obrigatório.');
|
||||
return $this->redirect('/admin/affiliatesadmin/banners/create');
|
||||
}
|
||||
|
||||
AffiliateBanner::create($data);
|
||||
$this->addFlash('success', __('affiliatesadmin.banner_created_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin/banners');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified banner.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function editBanner(Request $request, int $id): Response
|
||||
{
|
||||
$banner = AffiliateBanner::findOrFail($id);
|
||||
return $this->renderAdminView('@affiliatesadmin/banner-form.twig', ['banner' => $banner]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified banner in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function updateBanner(Request $request, int $id): Response
|
||||
{
|
||||
$banner = AffiliateBanner::findOrFail($id);
|
||||
$data = $request->request->all();
|
||||
|
||||
if (empty($data['affiliate_banners_name']) || empty($data['affiliate_banners_target_url'])) {
|
||||
$this->addFlash('error', __('affiliatesadmin.validation_banner_fields_required'));
|
||||
return $this->redirect('/admin/affiliatesadmin/banners/' . $id . '/edit');
|
||||
}
|
||||
|
||||
if ($request->files->has('banner_file')) {
|
||||
$file = $request->files->get('banner_file');
|
||||
if ($file && $file->isValid()) {
|
||||
// Delete old file if exists
|
||||
if ($banner->affiliate_banners_image_url) {
|
||||
$oldPath = dirname(__DIR__, 4) . '/storage/uploads/affiliatesadmin/' . $banner->affiliate_banners_image_url;
|
||||
if (file_exists($oldPath) && is_file($oldPath)) {
|
||||
unlink($oldPath);
|
||||
}
|
||||
}
|
||||
$data['affiliate_banners_image_url'] = $this->handleBannerUpload($file);
|
||||
}
|
||||
}
|
||||
|
||||
$banner->update($data);
|
||||
$this->addFlash('success', __('affiliatesadmin.banner_updated_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin/banners');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle banner image upload.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\File\UploadedFile $file
|
||||
* @return string|null
|
||||
*/
|
||||
private function handleBannerUpload($file): ?string
|
||||
{
|
||||
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/affiliatesadmin';
|
||||
if (!is_dir($uploadDir)) {
|
||||
if (!mkdir($uploadDir, 0777, true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$filename = uniqid('banner_') . '.' . $file->guessExtension();
|
||||
$file->move($uploadDir, $filename);
|
||||
|
||||
// Store ONLY the filename in the database
|
||||
// The view will prepend /storage/uploads/affiliatesadmin/
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified banner from storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function destroyBanner(Request $request, int $id): Response
|
||||
{
|
||||
$banner = AffiliateBanner::findOrFail($id);
|
||||
if ($banner->affiliate_banners_image_url) {
|
||||
$path = dirname(__DIR__, 4) . '/storage/uploads/affiliatesadmin/' . $banner->affiliate_banners_image_url;
|
||||
if (file_exists($path) && is_file($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
AffiliateBanner::destroy($id);
|
||||
$this->addFlash('success', __('affiliatesadmin.banner_deleted_successfully'));
|
||||
return $this->redirect('/admin/affiliatesadmin/banners');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display affiliate statistics.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function statistics(Request $request): Response
|
||||
{
|
||||
$this->checkPermission('affiliatesadmin.view_statistics');
|
||||
|
||||
$stats = [
|
||||
'total_affiliates' => Affiliate::count(),
|
||||
'total_commissions_approved' => AffiliateCommission::where('affiliate_commissions_status', 'approved')->sum('affiliate_commissions_amount'),
|
||||
'total_clicks' => AffiliateClick::count(),
|
||||
'conversion_rate' => 0,
|
||||
];
|
||||
|
||||
if ($stats['total_clicks'] > 0) {
|
||||
$approvedCount = AffiliateCommission::where('affiliate_commissions_status', 'approved')->count();
|
||||
$stats['conversion_rate'] = round(($approvedCount / $stats['total_clicks']) * 100, 2);
|
||||
}
|
||||
|
||||
return $this->renderAdminView('@affiliatesadmin/statistics.twig', [
|
||||
'stats' => $stats
|
||||
]);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Affiliate extends Model
|
||||
{
|
||||
protected $table = 'affiliates';
|
||||
|
||||
protected $primaryKey = 'affiliates_id';
|
||||
|
||||
protected $fillable = [
|
||||
'affiliates_code',
|
||||
'affiliates_name',
|
||||
'affiliates_email',
|
||||
'affiliates_phone',
|
||||
'affiliates_address',
|
||||
'affiliates_website',
|
||||
'affiliates_commission_rate',
|
||||
'affiliates_total_earned',
|
||||
'affiliates_total_paid',
|
||||
'affiliates_status',
|
||||
];
|
||||
|
||||
/**
|
||||
* The "booting" method of the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Ensure affiliates_code is generated before creating
|
||||
static::creating(function ($affiliate) {
|
||||
if (empty($affiliate->affiliates_code)) {
|
||||
$affiliate->affiliates_code = static::generateUniqueAffiliateCode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique affiliate code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateUniqueAffiliateCode(): string
|
||||
{
|
||||
$code = \Illuminate\Support\Str::random(10); // Using Laravel's Str helper
|
||||
while (static::where('affiliates_code', $code)->exists()) {
|
||||
$code = \Illuminate\Support\Str::random(10);
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the commissions for the affiliate.
|
||||
*/
|
||||
public function commissions()
|
||||
{
|
||||
return $this->hasMany(AffiliateCommission::class, 'affiliate_commissions_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the banners created by the affiliate.
|
||||
*/
|
||||
public function banners()
|
||||
{
|
||||
return $this->hasMany(AffiliateBanner::class, 'affiliate_banners_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clicks for the affiliate's banners.
|
||||
*/
|
||||
public function clicks()
|
||||
{
|
||||
return $this->hasMany(AffiliateClick::class, 'affiliate_clicks_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AffiliateBanner extends Model
|
||||
{
|
||||
protected $table = 'affiliate_banners';
|
||||
|
||||
protected $primaryKey = 'affiliate_banners_id';
|
||||
|
||||
protected $fillable = [
|
||||
'affiliate_banners_name',
|
||||
'affiliate_banners_description',
|
||||
'affiliate_banners_image_url',
|
||||
'affiliate_banners_target_url',
|
||||
'affiliate_banners_width',
|
||||
'affiliate_banners_height',
|
||||
'affiliate_banners_is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the affiliate that owns the banner.
|
||||
*/
|
||||
public function affiliate()
|
||||
{
|
||||
return $this->belongsTo(Affiliate::class, 'affiliate_banners_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clicks for the banner.
|
||||
*/
|
||||
public function clicks()
|
||||
{
|
||||
return $this->hasMany(AffiliateClick::class, 'affiliate_clicks_banner_id', 'affiliate_banners_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AffiliateClick extends Model
|
||||
{
|
||||
protected $table = 'affiliate_clicks';
|
||||
|
||||
protected $primaryKey = 'affiliate_clicks_id';
|
||||
|
||||
protected $fillable = [
|
||||
'affiliate_clicks_affiliate_id',
|
||||
'affiliate_clicks_banner_id',
|
||||
'affiliate_clicks_ip_address',
|
||||
'affiliate_clicks_user_agent',
|
||||
'affiliate_clicks_referrer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the affiliate that generated the click.
|
||||
*/
|
||||
public function affiliate()
|
||||
{
|
||||
return $this->belongsTo(Affiliate::class, 'affiliate_clicks_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the banner that was clicked.
|
||||
*/
|
||||
public function banner()
|
||||
{
|
||||
return $this->belongsTo(AffiliateBanner::class, 'affiliate_clicks_banner_id', 'affiliate_banners_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AffiliatesAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AffiliateCommission extends Model
|
||||
{
|
||||
protected $table = 'affiliate_commissions';
|
||||
|
||||
protected $primaryKey = 'affiliate_commissions_id';
|
||||
|
||||
protected $fillable = [
|
||||
'affiliate_commissions_affiliate_id',
|
||||
'affiliate_commissions_order_id',
|
||||
'affiliate_commissions_amount',
|
||||
'affiliate_commissions_commission_rate',
|
||||
'affiliate_commissions_status',
|
||||
'affiliate_commissions_notes',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the affiliate that owns the commission.
|
||||
*/
|
||||
public function affiliate()
|
||||
{
|
||||
return $this->belongsTo(Affiliate::class, 'affiliate_commissions_affiliate_id', 'affiliates_id');
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAffiliatesTable
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('affiliates')) {
|
||||
Capsule::schema()->create('affiliates', function (Blueprint $table) {
|
||||
$table->bigIncrements('affiliates_id');
|
||||
$table->string('affiliates_code')->unique();
|
||||
$table->string('affiliates_name');
|
||||
$table->string('affiliates_email')->unique();
|
||||
$table->string('affiliates_phone')->nullable();
|
||||
$table->text('affiliates_address')->nullable();
|
||||
$table->string('affiliates_website')->nullable();
|
||||
$table->decimal('affiliates_commission_rate', 5, 2)->default(10.00);
|
||||
$table->decimal('affiliates_total_earned', 12, 2)->default(0.00);
|
||||
$table->decimal('affiliates_total_paid', 12, 2)->default(0.00);
|
||||
$table->enum('affiliates_status', ['active', 'pending', 'suspended'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('affiliates');
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAffiliateCommissionsTable
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('affiliate_commissions')) {
|
||||
Capsule::schema()->create('affiliate_commissions', function (Blueprint $table) {
|
||||
$table->bigIncrements('affiliate_commissions_id');
|
||||
$table->unsignedBigInteger('affiliate_commissions_affiliate_id');
|
||||
$table->string('affiliate_commissions_order_id')->nullable(); // Or whatever identifier for the sale
|
||||
$table->decimal('affiliate_commissions_amount', 10, 2);
|
||||
$table->decimal('affiliate_commissions_commission_rate', 5, 2);
|
||||
$table->enum('affiliate_commissions_status', ['pending', 'approved', 'rejected', 'paid'])->default('pending');
|
||||
$table->text('affiliate_commissions_notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('affiliate_commissions_affiliate_id')->references('affiliates_id')->on('affiliates')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('affiliate_commissions');
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAffiliateBannersTable
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('affiliate_banners')) {
|
||||
Capsule::schema()->create('affiliate_banners', function (Blueprint $table) {
|
||||
$table->bigIncrements('affiliate_banners_id');
|
||||
$table->string('affiliate_banners_name');
|
||||
$table->text('affiliate_banners_description')->nullable();
|
||||
$table->string('affiliate_banners_image_url');
|
||||
$table->string('affiliate_banners_target_url')->nullable();
|
||||
$table->integer('affiliate_banners_width')->nullable();
|
||||
$table->integer('affiliate_banners_height')->nullable();
|
||||
$table->boolean('affiliate_banners_is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('affiliate_banners');
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAffiliateClicksTable
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('affiliate_clicks')) {
|
||||
Capsule::schema()->create('affiliate_clicks', function (Blueprint $table) {
|
||||
$table->bigIncrements('affiliate_clicks_id');
|
||||
$table->unsignedBigInteger('affiliate_clicks_affiliate_id');
|
||||
$table->unsignedBigInteger('affiliate_clicks_banner_id');
|
||||
$table->string('affiliate_clicks_ip_address')->nullable();
|
||||
$table->text('affiliate_clicks_user_agent')->nullable();
|
||||
$table->string('affiliate_clicks_referrer')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('affiliate_clicks_affiliate_id')->references('affiliates_id')->on('affiliates')->onDelete('cascade');
|
||||
$table->foreign('affiliate_clicks_banner_id')->references('affiliate_banners_id')->on('affiliate_banners')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('affiliate_clicks');
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
|
||||
class RegisterAffiliatesModules
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
// Register modules in 'modules' table
|
||||
$modules = [
|
||||
[
|
||||
'modules_name' => 'affiliatesadmin',
|
||||
'modules_display_name' => 'Gerenciamento de Afiliados',
|
||||
'modules_description' => 'Módulo administrativo para gerenciar afiliados, comissões e banners',
|
||||
'modules_version' => '1.0.0',
|
||||
'modules_author' => 'SysCMS Team',
|
||||
'modules_dependencies' => json_encode(['auth']),
|
||||
'modules_ative' => 1,
|
||||
'modules_public' => 0,
|
||||
'modules_blocked' => 0,
|
||||
'modules_settings' => json_encode([
|
||||
'commission_rate_default' => 10,
|
||||
'commission_min_payout' => 50,
|
||||
'banner_default_width' => 300,
|
||||
'banner_default_height' => 250
|
||||
]),
|
||||
'modules_permissions' => json_encode([
|
||||
'affiliatesadmin.affiliates.view',
|
||||
'affiliatesadmin.affiliates.create',
|
||||
'affiliatesadmin.affiliates.edit',
|
||||
'affiliatesadmin.affiliates.delete',
|
||||
'affiliatesadmin.commissions.view',
|
||||
'affiliatesadmin.commissions.approve',
|
||||
'affiliatesadmin.commissions.reject',
|
||||
'affiliatesadmin.commissions.pay',
|
||||
'affiliatesadmin.banners.view',
|
||||
'affiliatesadmin.banners.create',
|
||||
'affiliatesadmin.banners.edit',
|
||||
'affiliatesadmin.banners.delete',
|
||||
'affiliatesadmin.statistics.view'
|
||||
]),
|
||||
'modules_path' => 'modules/affiliatesadmin',
|
||||
'modules_namespace' => 'Modules\\AffiliatesAdmin',
|
||||
'modules_icon' => 'bi bi-people',
|
||||
'menu_side_bar' => 1,
|
||||
'menu_nav_bar' => 0,
|
||||
'modules_order' => 80,
|
||||
'modules_install_date' => date('Y-m-d H:i:s'),
|
||||
'modules_update_date' => date('Y-m-d H:i:s')
|
||||
],
|
||||
[
|
||||
'modules_name' => 'affiliates',
|
||||
'modules_display_name' => 'Área de Afiliados',
|
||||
'modules_description' => 'Módulo para afiliados gerenciarem sua conta e banners',
|
||||
'modules_version' => '1.0.0',
|
||||
'modules_author' => 'SysCMS Team',
|
||||
'modules_dependencies' => json_encode(['auth', 'affiliatesadmin']),
|
||||
'modules_ative' => 1,
|
||||
'modules_public' => 1,
|
||||
'modules_blocked' => 0,
|
||||
'modules_settings' => null,
|
||||
'modules_permissions' => json_encode([
|
||||
'affiliates.dashboard.view',
|
||||
'affiliates.banners.view',
|
||||
'affiliates.banners.get_code',
|
||||
'affiliates.commissions.view',
|
||||
'affiliates.profile.view',
|
||||
'affiliates.profile.edit'
|
||||
]),
|
||||
'modules_path' => 'modules/affiliates',
|
||||
'modules_namespace' => 'Modules\\Affiliates',
|
||||
'modules_icon' => 'bi bi-person-badge',
|
||||
'menu_side_bar' => 0,
|
||||
'menu_nav_bar' => 0,
|
||||
'modules_order' => 90,
|
||||
'modules_install_date' => date('Y-m-d H:i:s'),
|
||||
'modules_update_date' => date('Y-m-d H:i:s')
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($modules as $moduleData) {
|
||||
$moduleName = $moduleData['modules_name'];
|
||||
$existing = Capsule::table('modules')->where('modules_name', $moduleName)->first();
|
||||
|
||||
if (!$existing) {
|
||||
$moduleId = Capsule::table('modules')->insertGetId($moduleData);
|
||||
} else {
|
||||
$moduleId = $existing->modules_id;
|
||||
Capsule::table('modules')->where('modules_name', $moduleName)->update([
|
||||
'modules_display_name' => $moduleData['modules_display_name'],
|
||||
'modules_description' => $moduleData['modules_description'],
|
||||
'modules_version' => $moduleData['modules_version'],
|
||||
'modules_author' => $moduleData['modules_author'],
|
||||
'modules_dependencies' => $moduleData['modules_dependencies'],
|
||||
'modules_ative' => $moduleData['modules_ative'],
|
||||
'modules_public' => $moduleData['modules_public'],
|
||||
'modules_blocked' => $moduleData['modules_blocked'],
|
||||
'modules_settings' => $moduleData['modules_settings'],
|
||||
'modules_permissions' => $moduleData['modules_permissions'],
|
||||
'modules_path' => $moduleData['modules_path'],
|
||||
'modules_namespace' => $moduleData['modules_namespace'],
|
||||
'modules_icon' => $moduleData['modules_icon'],
|
||||
'menu_side_bar' => $moduleData['menu_side_bar'],
|
||||
'menu_nav_bar' => $moduleData['menu_nav_bar'],
|
||||
'modules_order' => $moduleData['modules_order'],
|
||||
'modules_update_date' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
// Associate module with all existing companies in 'bus_modules'
|
||||
if (Capsule::schema()->hasTable('bus')) {
|
||||
$companies = Capsule::table('bus')->get();
|
||||
foreach ($companies as $company) {
|
||||
$existingBusModule = Capsule::table('bus_modules')
|
||||
->where('bus_modules_bus_id', $company->bus_id)
|
||||
->where('bus_modules_module_id', $moduleId)
|
||||
->first();
|
||||
|
||||
if (!$existingBusModule) {
|
||||
Capsule::table('bus_modules')->insert([
|
||||
'bus_modules_bus_id' => $company->bus_id,
|
||||
'bus_modules_module_id' => $moduleId,
|
||||
'bus_modules_is_enabled' => 1,
|
||||
'bus_modules_show_public_nav' => $moduleData['modules_public'],
|
||||
'bus_modules_show_dashboard_nav' => 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register permissions in 'role_permissions' table for admin and member roles
|
||||
$permissions = [
|
||||
// AffiliatesAdmin permissions
|
||||
'affiliatesadmin.affiliates.view',
|
||||
'affiliatesadmin.affiliates.create',
|
||||
'affiliatesadmin.affiliates.edit',
|
||||
'affiliatesadmin.affiliates.delete',
|
||||
'affiliatesadmin.commissions.view',
|
||||
'affiliatesadmin.commissions.approve',
|
||||
'affiliatesadmin.commissions.reject',
|
||||
'affiliatesadmin.commissions.pay',
|
||||
'affiliatesadmin.banners.view',
|
||||
'affiliatesadmin.banners.create',
|
||||
'affiliatesadmin.banners.edit',
|
||||
'affiliatesadmin.banners.delete',
|
||||
'affiliatesadmin.statistics.view',
|
||||
// Affiliates permissions
|
||||
'affiliates.dashboard.view',
|
||||
'affiliates.banners.view',
|
||||
'affiliates.banners.get_code',
|
||||
'affiliates.commissions.view',
|
||||
'affiliates.profile.view',
|
||||
'affiliates.profile.edit',
|
||||
];
|
||||
|
||||
$defaultCompanyId = 1;
|
||||
foreach (['admin', 'member'] as $role) {
|
||||
foreach ($permissions as $perm) {
|
||||
$existing = Capsule::table('role_permissions')
|
||||
->where('role_permissions_role', $role)
|
||||
->where('role_permissions_bus_id', $defaultCompanyId)
|
||||
->where('role_permissions_permission', $perm)
|
||||
->first();
|
||||
if (!$existing) {
|
||||
Capsule::table('role_permissions')->insert([
|
||||
'role_permissions_role' => $role,
|
||||
'role_permissions_bus_id' => $defaultCompanyId,
|
||||
'role_permissions_permission' => $perm,
|
||||
'role_permissions_created_by' => null,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
// Remove permissions from 'role_permissions' for admin and member roles
|
||||
$defaultCompanyId = 1;
|
||||
$permissions = [
|
||||
// AffiliatesAdmin permissions
|
||||
'affiliatesadmin.affiliates.view',
|
||||
'affiliatesadmin.affiliates.create',
|
||||
'affiliatesadmin.affiliates.edit',
|
||||
'affiliatesadmin.affiliates.delete',
|
||||
'affiliatesadmin.commissions.view',
|
||||
'affiliatesadmin.commissions.approve',
|
||||
'affiliatesadmin.commissions.reject',
|
||||
'affiliatesadmin.commissions.pay',
|
||||
'affiliatesadmin.banners.view',
|
||||
'affiliatesadmin.banners.create',
|
||||
'affiliatesadmin.banners.edit',
|
||||
'affiliatesadmin.banners.delete',
|
||||
'affiliatesadmin.statistics.view',
|
||||
// Affiliates permissions
|
||||
'affiliates.dashboard.view',
|
||||
'affiliates.banners.view',
|
||||
'affiliates.banners.get_code',
|
||||
'affiliates.commissions.view',
|
||||
'affiliates.profile.view',
|
||||
'affiliates.profile.edit',
|
||||
];
|
||||
|
||||
foreach (['admin', 'member'] as $role) {
|
||||
foreach ($permissions as $perm) {
|
||||
Capsule::table('role_permissions')
|
||||
->where('role_permissions_role', $role)
|
||||
->where('role_permissions_bus_id', $defaultCompanyId)
|
||||
->where('role_permissions_permission', $perm)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from bus_modules
|
||||
$moduleNames = ['affiliatesadmin', 'affiliates'];
|
||||
foreach ($moduleNames as $moduleName) {
|
||||
$module = Capsule::table('modules')->where('modules_name', $moduleName)->first();
|
||||
if ($module) {
|
||||
Capsule::table('bus_modules')
|
||||
->where('bus_modules_module_id', $module->modules_id)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from modules table
|
||||
foreach ($moduleNames as $moduleName) {
|
||||
Capsule::table('modules')->where('modules_name', $moduleName)->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
|
||||
class AddMenuToAffiliatesadmin
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
// Get the affiliatesadmin module
|
||||
$module = Capsule::table('modules')->where('modules_name', 'affiliatesadmin')->first();
|
||||
|
||||
if ($module) {
|
||||
// Get current settings
|
||||
$settings = $module->modules_settings ? json_decode($module->modules_settings, true) : [];
|
||||
|
||||
// Add menu configuration
|
||||
$settings['menu'] = [
|
||||
'dashboard' => [
|
||||
'icon' => 'bi bi-people',
|
||||
'label' => 'Gerenciamento de Afiliados',
|
||||
'route' => 'admin/affiliatesadmin/affiliates',
|
||||
'order' => 80
|
||||
]
|
||||
];
|
||||
|
||||
// Update the module
|
||||
Capsule::table('modules')
|
||||
->where('modules_name', 'affiliatesadmin')
|
||||
->update([
|
||||
'modules_settings' => json_encode($settings)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
// Remove menu configuration (optional rollback)
|
||||
$module = Capsule::table('modules')->where('modules_name', 'affiliatesadmin')->first();
|
||||
|
||||
if ($module) {
|
||||
$settings = $module->modules_settings ? json_decode($module->modules_settings, true) : [];
|
||||
unset($settings['menu']);
|
||||
|
||||
Capsule::table('modules')
|
||||
->where('modules_name', 'affiliatesadmin')
|
||||
->update([
|
||||
'modules_settings' => json_encode($settings)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'module_name' => 'Gerenciamento de Afiliados',
|
||||
'affiliates' => 'Afiliados',
|
||||
'affiliate' => 'Afiliado',
|
||||
'affiliate_code' => 'Código de Afiliado',
|
||||
'name' => 'Nome',
|
||||
'email' => 'Email',
|
||||
'phone' => 'Telefone',
|
||||
'address' => 'Endereço',
|
||||
'website' => 'Website',
|
||||
'commission_rate' => 'Taxa de Comissão',
|
||||
'total_earned' => 'Total Ganho',
|
||||
'total_paid' => 'Total Pago',
|
||||
'status' => 'Status',
|
||||
'actions' => 'Ações',
|
||||
'create_affiliate' => 'Criar Afiliado',
|
||||
'edit_affiliate' => 'Editar Afiliado',
|
||||
'view_affiliate' => 'Ver Afiliado',
|
||||
'delete_affiliate' => 'Excluir Afiliado',
|
||||
'commissions' => 'Comissões',
|
||||
'commission' => 'Comissão',
|
||||
'approve_commission' => 'Aprovar Comissão',
|
||||
'reject_commission' => 'Rejeitar Comissão',
|
||||
'banners' => 'Banners',
|
||||
'banner' => 'Banner',
|
||||
'create_banner' => 'Criar Banner',
|
||||
'edit_banner' => 'Editar Banner',
|
||||
'delete_banner' => 'Excluir Banner',
|
||||
'statistics' => 'Estatísticas',
|
||||
'id' => 'ID',
|
||||
'created_at' => 'Criado em',
|
||||
'updated_at' => 'Atualizado em',
|
||||
'save' => 'Salvar',
|
||||
'cancel' => 'Cancelar',
|
||||
'back_to_list' => 'Voltar para a lista',
|
||||
'confirm_delete' => 'Tem certeza que deseja excluir este item?',
|
||||
|
||||
// Validation messages
|
||||
'validation_name_email_required' => 'Nome e Email são obrigatórios.',
|
||||
'validation_invalid_email' => 'O endereço de email é inválido.',
|
||||
'validation_email_exists' => 'Este email já está em uso por outro afiliado.',
|
||||
'validation_banner_fields_required' => 'Nome, URL da Imagem e URL de Destino são obrigatórios para o banner.',
|
||||
'validation_invalid_url' => 'A URL da Imagem ou a URL de Destino são inválidas.',
|
||||
|
||||
// Success messages
|
||||
'affiliate_created_successfully' => 'Afiliado criado com sucesso!',
|
||||
'affiliate_updated_successfully' => 'Afiliado atualizado com sucesso!',
|
||||
'affiliate_deleted_successfully' => 'Afiliado excluído com sucesso!',
|
||||
'commission_approved_successfully' => 'Comissão aprovada com sucesso!',
|
||||
'commission_rejected_successfully' => 'Comissão rejeitada com sucesso!',
|
||||
'banner_created_successfully' => 'Banner criado com sucesso!',
|
||||
'banner_updated_successfully' => 'Banner atualizado com sucesso!',
|
||||
'banner_deleted_successfully' => 'Banner excluído com sucesso!',
|
||||
];
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "affiliatesadmin",
|
||||
"display_name": "Gerenciamento de Afiliados",
|
||||
"description": "Módulo administrativo para gerenciar afiliados, comissões e banners",
|
||||
"version": "1.0.0",
|
||||
"author": "SysCMS Team",
|
||||
"dependencies": ["auth"],
|
||||
"permissions": [
|
||||
"affiliatesadmin.affiliates.view",
|
||||
"affiliatesadmin.affiliates.create",
|
||||
"affiliatesadmin.affiliates.edit",
|
||||
"affiliatesadmin.affiliates.delete",
|
||||
"affiliatesadmin.commissions.view",
|
||||
"affiliatesadmin.commissions.approve",
|
||||
"affiliatesadmin.commissions.reject",
|
||||
"affiliatesadmin.commissions.pay",
|
||||
"affiliatesadmin.banners.view",
|
||||
"affiliatesadmin.banners.create",
|
||||
"affiliatesadmin.banners.edit",
|
||||
"affiliatesadmin.banners.delete",
|
||||
"affiliatesadmin.statistics.view"
|
||||
],
|
||||
"providers": ["Modules\\AffiliatesAdmin\\Providers\\AffiliatesAdminServiceProvider"],
|
||||
"settings": {
|
||||
"commission_rate_default": 10,
|
||||
"commission_min_payout": 50,
|
||||
"banner_default_width": 300,
|
||||
"banner_default_height": 250
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": {
|
||||
"icon": "bi bi-people",
|
||||
"label": "affiliatesadmin.menu.dashboard",
|
||||
"route": "admin/affiliatesadmin/affiliates",
|
||||
"order": 50
|
||||
}
|
||||
},
|
||||
"routes": {"web": "routes/web.php"},
|
||||
"views": "views",
|
||||
"locales": "locales",
|
||||
"module_active": true,
|
||||
"module_public": false,
|
||||
"module_blocked": false,
|
||||
"ready": true,
|
||||
"tables": ["affiliates", "affiliate_commissions", "affiliate_banners", "affiliate_clicks"]
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Kernel\Application;
|
||||
use Modules\AffiliatesAdmin\Controllers\AffiliatesAdminController;
|
||||
|
||||
return function (Application $app) {
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin', [AffiliatesAdminController::class, 'index']);
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/affiliates', [AffiliatesAdminController::class, 'index']);
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/create', [AffiliatesAdminController::class, 'create']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/store', [AffiliatesAdminController::class, 'store']);
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/{id:\d+}/edit', [AffiliatesAdminController::class, 'edit']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/{id:\d+}/update', [AffiliatesAdminController::class, 'update']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/{id:\d+}/delete', [AffiliatesAdminController::class, 'destroy']);
|
||||
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/commissions', [AffiliatesAdminController::class, 'commissions']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/commissions/{id:\d+}/approve', [AffiliatesAdminController::class, 'approveCommission']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/commissions/{id:\d+}/reject', [AffiliatesAdminController::class, 'rejectCommission']);
|
||||
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/banners', [AffiliatesAdminController::class, 'banners']);
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/banners/create', [AffiliatesAdminController::class, 'createBanner']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/banners/store', [AffiliatesAdminController::class, 'storeBanner']);
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/banners/{id:\d+}/edit', [AffiliatesAdminController::class, 'editBanner']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/banners/{id:\d+}/update', [AffiliatesAdminController::class, 'updateBanner']);
|
||||
$app->addRoute('POST', '/admin/affiliatesadmin/banners/{id:\d+}/delete', [AffiliatesAdminController::class, 'destroyBanner']);
|
||||
|
||||
$app->addRoute('GET', '/admin/affiliatesadmin/statistics', [AffiliatesAdminController::class, 'statistics']);
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">{% if banner.affiliate_banners_id %}Editar Banner{% else %}Criar Novo Banner{% endif %}</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="/admin/affiliatesadmin/affiliates">Afiliados</a></li>
|
||||
<li class="breadcrumb-item"><a href="/admin/affiliatesadmin/banners">Banners</a></li>
|
||||
<li class="breadcrumb-item active">{% if banner.affiliate_banners_id %}Editar{% else %}Novo{% endif %}</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-image me-1"></i>
|
||||
Formulário de Banner
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{% if banner.affiliate_banners_id %}/admin/affiliatesadmin/banners/{{ banner.affiliate_banners_id }}/update{% else %}/admin/affiliatesadmin/banners/store{% endif %}" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="affiliate_banners_name" class="form-label">Nome:</label>
|
||||
<input type="text" class="form-control" id="affiliate_banners_name" name="affiliate_banners_name" value="{{ banner.affiliate_banners_name ?? '' }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="affiliate_banners_description" class="form-label">Descrição:</label>
|
||||
<textarea class="form-control" id="affiliate_banners_description" name="affiliate_banners_description" rows="2">{{ banner.affiliate_banners_description ?? '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="banner_file" class="form-label">Arquivo do Banner:</label>
|
||||
<input type="file" class="form-control" id="banner_file" name="banner_file" {% if not banner.affiliate_banners_id %}required{% endif %}>
|
||||
<div class="form-text text-muted">Selecione uma imagem para o banner (JPG, PNG, GIF).</div>
|
||||
{% if banner.affiliate_banners_image_url %}
|
||||
<div class="mt-2">
|
||||
<p class="small mb-1">Banner Atual:</p>
|
||||
<img src="/storage/uploads/affiliatesadmin/{{ banner.affiliate_banners_image_url }}" alt="Banner Atual" class="img-thumbnail" style="max-height: 150px;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="affiliate_banners_target_url" class="form-label">URL de Destino:</label>
|
||||
<input type="url" class="form-control" id="affiliate_banners_target_url" name="affiliate_banners_target_url" value="{{ banner.affiliate_banners_target_url ?? '' }}">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliate_banners_width" class="form-label">Largura (px):</label>
|
||||
<input type="number" class="form-control" id="affiliate_banners_width" name="affiliate_banners_width" value="{{ banner.affiliate_banners_width ?? '' }}">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliate_banners_height" class="form-label">Altura (px):</label>
|
||||
<input type="number" class="form-control" id="affiliate_banners_height" name="affiliate_banners_height" value="{{ banner.affiliate_banners_height ?? '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="affiliate_banners_is_active" name="affiliate_banners_is_active" value="1" {% if banner.affiliate_banners_is_active ?? true %}checked{% endif %}>
|
||||
<label class="form-check-label" for="affiliate_banners_is_active">Banner Ativo</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Salvar
|
||||
</button>
|
||||
<a href="/admin/affiliatesadmin/banners" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Cancelar
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gerenciamento de Banners</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="/admin/affiliatesadmin/affiliates">Afiliados</a></li>
|
||||
<li class="breadcrumb-item active">Banners</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<i class="bi bi-image me-1"></i>
|
||||
Lista de Banners
|
||||
</div>
|
||||
<a href="/admin/affiliatesadmin/banners/create" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-plus-lg"></i> Novo Banner
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nome</th>
|
||||
<th>Imagem</th>
|
||||
<th>URL de Destino</th>
|
||||
<th>Ativo</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for banner in banners %}
|
||||
<tr>
|
||||
<td>{{ banner.affiliate_banners_id }}</td>
|
||||
<td>{{ banner.affiliate_banners_name }}</td>
|
||||
<td>
|
||||
<a href="/storage/uploads/affiliatesadmin/{{ banner.affiliate_banners_image_url }}" data-lightbox="banner-{{ banner.affiliate_banners_id }}">
|
||||
<img src="/storage/uploads/affiliatesadmin/{{ banner.affiliate_banners_image_url }}" alt="{{ banner.affiliate_banners_name }}" class="img-thumbnail" style="max-width: 80px;">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ banner.affiliate_banners_target_url }}" target="_blank" class="text-truncate d-inline-block" style="max-width: 200px;">
|
||||
{{ banner.affiliate_banners_target_url }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ banner.affiliate_banners_is_active ? 'success' : 'secondary' }}">
|
||||
{{ banner.affiliate_banners_is_active ? 'Sim' : 'Não' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/admin/affiliatesadmin/banners/{{ banner.affiliate_banners_id }}/edit" class="btn btn-info btn-sm" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form action="/admin/affiliatesadmin/banners/{{ banner.affiliate_banners_id }}/delete" method="POST" onsubmit="return confirm('Tem certeza que deseja excluir este banner?')">
|
||||
<button type="submit" class="btn btn-danger btn-sm" title="Excluir">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#dataTable').DataTable({
|
||||
language: {
|
||||
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||
},
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gerenciamento de Comissões</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="/admin/affiliatesadmin/affiliates">Afiliados</a></li>
|
||||
<li class="breadcrumb-item active">Comissões</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-currency-dollar me-1"></i>
|
||||
Lista de Comissões
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Afiliado</th>
|
||||
<th>ID do Pedido</th>
|
||||
<th>Valor</th>
|
||||
<th>Taxa (%)</th>
|
||||
<th>Status</th>
|
||||
<th>Notas</th>
|
||||
<th>Data</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for commission in commissions %}
|
||||
<tr>
|
||||
<td>{{ commission.affiliate_commissions_id }}</td>
|
||||
<td>{{ commission.affiliate.affiliates_name ?? 'N/A' }} <br><small class="text-muted">({{ commission.affiliate.affiliates_code ?? 'N/A' }})</small></td>
|
||||
<td>{{ commission.affiliate_commissions_order_id ?? 'N/A' }}</td>
|
||||
<td>R$ {{ commission.affiliate_commissions_amount|number_format(2, ',', '.') }}</td>
|
||||
<td>{{ commission.affiliate_commissions_commission_rate }}%</td>
|
||||
<td>
|
||||
{% set statusColors = {
|
||||
'pending': 'warning',
|
||||
'approved': 'success',
|
||||
'rejected': 'danger',
|
||||
'paid': 'info'
|
||||
} %}
|
||||
<span class="badge bg-{{ statusColors[commission.affiliate_commissions_status] ?? 'secondary' }}">
|
||||
{{ commission.affiliate_commissions_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ commission.affiliate_commissions_notes ?? 'N/A' }}</td>
|
||||
<td>{{ commission.created_at|date("d/m/Y H:i") }}</td>
|
||||
<td>
|
||||
{% if commission.affiliate_commissions_status == 'pending' %}
|
||||
<div class="btn-group" role="group">
|
||||
<form action="/admin/affiliatesadmin/commissions/{{ commission.affiliate_commissions_id }}/approve" method="POST">
|
||||
<button type="submit" class="btn btn-success btn-sm" title="Aprovar">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form action="/admin/affiliatesadmin/commissions/{{ commission.affiliate_commissions_id }}/reject" method="POST">
|
||||
<button type="submit" class="btn btn-warning btn-sm" title="Rejeitar">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted small">Processado</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#dataTable').DataTable({
|
||||
language: {
|
||||
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||
},
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">{% if affiliate.affiliates_id %}Editar Afiliado{% else %}Criar Novo Afiliado{% endif %}</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="/admin/affiliatesadmin/affiliates">Afiliados</a></li>
|
||||
<li class="breadcrumb-item active">{% if affiliate.affiliates_id %}Editar{% else %}Novo{% endif %}</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-person-plus me-1"></i>
|
||||
Formulário de Afiliado
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{% if affiliate.affiliates_id %}/admin/affiliatesadmin/{{ affiliate.affiliates_id }}/update{% else %}/admin/affiliatesadmin/store{% endif %}" method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_name" class="form-label">Nome:</label>
|
||||
<input type="text" class="form-control" id="affiliates_name" name="affiliates_name" value="{{ affiliate.affiliates_name ?? '' }}" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_email" class="form-label">Email:</label>
|
||||
<input type="email" class="form-control" id="affiliates_email" name="affiliates_email" value="{{ affiliate.affiliates_email ?? '' }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_phone" class="form-label">Telefone:</label>
|
||||
<input type="text" class="form-control" id="affiliates_phone" name="affiliates_phone" value="{{ affiliate.affiliates_phone ?? '' }}">
|
||||
</div>
|
||||
{% if not affiliate.affiliates_id %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="password" class="form-label">Senha do Usuário:</label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
<div class="form-text text-muted">Obrigatória apenas para novos cadastros. Se o e-mail já existir no sistema, esta senha será ignorada e o usuário será vinculado.</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_website" class="form-label">Website:</label>
|
||||
<input type="url" class="form-control" id="affiliates_website" name="affiliates_website" value="{{ affiliate.affiliates_website ?? '' }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not affiliate.affiliates_id %}
|
||||
<div class="mb-3">
|
||||
<label for="affiliates_website" class="form-label">Website:</label>
|
||||
<input type="url" class="form-control" id="affiliates_website" name="affiliates_website" value="{{ affiliate.affiliates_website ?? '' }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="affiliates_address" class="form-label">Endereço:</label>
|
||||
<textarea class="form-control" id="affiliates_address" name="affiliates_address" rows="3">{{ affiliate.affiliates_address ?? '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_commission_rate" class="form-label">Taxa de Comissão (%):</label>
|
||||
<input type="number" step="0.01" class="form-control" id="affiliates_commission_rate" name="affiliates_commission_rate" value="{{ affiliate.affiliates_commission_rate ?? '10.00' }}" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="affiliates_status" class="form-label">Status:</label>
|
||||
<select class="form-select" id="affiliates_status" name="affiliates_status" required>
|
||||
<option value="active" {% if affiliate.affiliates_status ?? '' == 'active' %}selected{% endif %}>Ativo</option>
|
||||
<option value="pending" {% if affiliate.affiliates_status ?? '' == 'pending' %}selected{% endif %}>Pendente</option>
|
||||
<option value="suspended" {% if affiliate.affiliates_status ?? '' == 'suspended' %}selected{% endif %}>Suspenso</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Salvar
|
||||
</button>
|
||||
<a href="/admin/affiliatesadmin/affiliates" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Cancelar
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gerenciamento de Afiliados</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Afiliados</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-people me-1"></i>
|
||||
Lista de Afiliados
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nome</th>
|
||||
<th>Email</th>
|
||||
<th>Código</th>
|
||||
<th>Comissão</th>
|
||||
<th>Status</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for affiliate in affiliates %}
|
||||
<tr>
|
||||
<td>{{ affiliate.affiliates_id }}</td>
|
||||
<td>{{ affiliate.affiliates_name }}</td>
|
||||
<td>{{ affiliate.affiliates_email }}</td>
|
||||
<td><span class="badge bg-secondary">{{ affiliate.affiliates_code }}</span></td>
|
||||
<td>{{ affiliate.affiliates_commission_rate }}%</td>
|
||||
<td>
|
||||
{% set statusColors = {
|
||||
'active': 'success',
|
||||
'pending': 'warning',
|
||||
'suspended': 'danger'
|
||||
} %}
|
||||
<span class="badge bg-{{ statusColors[affiliate.affiliates_status] ?? 'secondary' }}">
|
||||
{{ affiliate.affiliates_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/admin/affiliatesadmin/{{ affiliate.affiliates_id }}/edit" class="btn btn-info btn-sm" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form action="/admin/affiliatesadmin/{{ affiliate.affiliates_id }}/delete" method="POST" onsubmit="return confirm('Tem certeza que deseja excluir este afiliado?')">
|
||||
<button type="submit" class="btn btn-danger btn-sm" title="Excluir">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#dataTable').DataTable({
|
||||
language: {
|
||||
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||
},
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<div class="mb-4 d-print-none">
|
||||
<div class="btn-group flex-wrap" role="group" aria-label="Navegação de Afiliados">
|
||||
<a href="/admin/affiliatesadmin/statistics" class="btn btn-outline-primary">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
</a>
|
||||
<a href="/admin/affiliatesadmin/affiliates" class="btn btn-outline-primary">
|
||||
<i class="bi bi-people"></i> Afiliados
|
||||
</a>
|
||||
<a href="/admin/affiliatesadmin/commissions" class="btn btn-outline-primary">
|
||||
<i class="bi bi-currency-dollar"></i> Comissões
|
||||
</a>
|
||||
<a href="/admin/affiliatesadmin/banners" class="btn btn-outline-primary">
|
||||
<i class="bi bi-image"></i> Banners
|
||||
</a>
|
||||
<a href="/admin/affiliatesadmin/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg"></i> Novo Afiliado
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block title %}{{ affiliate.name }} - {{ __("affiliatesadmin.affiliate_details") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ affiliate.name }}</h3>
|
||||
<div class="card-tools">
|
||||
<a href="/admin/affiliatesadmin/{{ affiliate.id }}/edit" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-edit"></i> {{ __("affiliatesadmin.edit") }}
|
||||
</a>
|
||||
<a href="/admin/affiliatesadmin" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-list"></i> {{ __("affiliatesadmin.back_to_list") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><strong>{{ __("affiliatesadmin.affiliate_code") }}:</strong> {{ affiliate.affiliate_code }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.name") }}:</strong> {{ affiliate.name }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.email") }}:</strong> {{ affiliate.email }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.phone") }}:</strong> {{ affiliate.phone }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.address") }}:</strong> {{ affiliate.address }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.website") }}:</strong> {{ affiliate.website }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p><strong>{{ __("affiliatesadmin.commission_rate") }}:</strong> {{ affiliate.commission_rate }}%</p>
|
||||
<p><strong>{{ __("affiliatesadmin.total_earned") }}:</strong> {{ affiliate.total_earned|number_format(2, ',', '.') }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.total_paid") }}:</strong> {{ affiliate.total_paid|number_format(2, ',', '.') }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.status") }}:</strong> {{ affiliate.status }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.created_at") }}:</strong> {{ affiliate.created_at|date("d/m/Y H:i") }}</p>
|
||||
<p><strong>{{ __("affiliatesadmin.updated_at") }}:</strong> {{ affiliate.updated_at|date("d/m/Y H:i") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Estatísticas de Afiliados</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/admin/dashboard">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Estatísticas</li>
|
||||
</ol>
|
||||
|
||||
{% include '@affiliatesadmin/nav.twig' %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-primary text-white mb-4">
|
||||
<div class="card-body">Total de Afiliados</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">{{ stats.total_affiliates ?? 0 }}</h4>
|
||||
<i class="bi bi-people-fill fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-success text-white mb-4">
|
||||
<div class="card-body">Comissões Aprovadas</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">R$ {{ stats.total_commissions_approved ?? 0|number_format(2, ',', '.') }}</h4>
|
||||
<i class="bi bi-cash-stack fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-warning text-white mb-4">
|
||||
<div class="card-body">Cliques nos Banners</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">{{ stats.total_clicks ?? 0 }}</h4>
|
||||
<i class="bi bi-mouse2 fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-info text-white mb-4">
|
||||
<div class="card-body">Conversão Média</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">{{ stats.conversion_rate ?? 0 }}%</h4>
|
||||
<i class="bi bi-graph-up-arrow fs-2 opacity-50"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-graph-up me-1"></i>
|
||||
Gráfico de Ganhos por Mês
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="myAreaChart" width="100%" height="40"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var ctx = document.getElementById("myAreaChart");
|
||||
if (ctx) {
|
||||
var myLineChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
|
||||
datasets: [{
|
||||
label: "Comissões (R$)",
|
||||
lineTension: 0.3,
|
||||
backgroundColor: "rgba(78, 115, 223, 0.05)",
|
||||
borderColor: "rgba(78, 115, 223, 1)",
|
||||
pointRadius: 3,
|
||||
pointBackgroundColor: "rgba(78, 115, 223, 1)",
|
||||
pointBorderColor: "rgba(78, 115, 223, 1)",
|
||||
pointHoverRadius: 3,
|
||||
pointHoverBackgroundColor: "rgba(78, 115, 223, 1)",
|
||||
pointHoverBorderColor: "rgba(78, 115, 223, 1)",
|
||||
pointHitRadius: 10,
|
||||
pointBorderWidth: 2,
|
||||
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Dados reais viriam do controller
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user