Initial Ladill Queue release — enterprise QMS standalone app.
Deploy Ladill Queue / deploy (push) Successful in 56s
Deploy Ladill Queue / deploy (push) Successful in 56s
Phases 1–6: tickets, counters, displays, appointments, workflows, rules, analytics, reports, feedback, admin, device API, and Gitea deploy workflow for queue.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DatabaseTime
|
||||
{
|
||||
public static function diffSeconds(string $fromColumn, string $toColumn): string
|
||||
{
|
||||
return match (DB::connection()->getDriverName()) {
|
||||
'sqlite' => "CAST((julianday({$toColumn}) - julianday({$fromColumn})) * 86400 AS INTEGER)",
|
||||
default => "TIMESTAMPDIFF(SECOND, {$fromColumn}, {$toColumn})",
|
||||
};
|
||||
}
|
||||
|
||||
public static function hourOf(string $column): string
|
||||
{
|
||||
return match (DB::connection()->getDriverName()) {
|
||||
'sqlite' => "CAST(strftime('%H', {$column}) AS INTEGER)",
|
||||
default => "HOUR({$column})",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class LadillAppUrl
|
||||
{
|
||||
public static function connect(string $appSubdomain, string $path = '/'): string
|
||||
{
|
||||
$root = (string) config('app.platform_domain', 'ladill.com');
|
||||
$path = '/'.ltrim($path, '/');
|
||||
$target = "https://{$appSubdomain}.{$root}{$path}";
|
||||
|
||||
return "https://{$appSubdomain}.{$root}/sso/connect?redirect=".urlencode($target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
class MobileTopbar
|
||||
{
|
||||
public static function resolve(): array
|
||||
{
|
||||
$appName = config('mobile-topbar.app_name', 'Ladill');
|
||||
|
||||
$title = $appName === 'Ladill'
|
||||
? 'Ladill'
|
||||
: "Ladill {$appName}";
|
||||
|
||||
return [
|
||||
'mobileTopbarTitle' => $title,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class OrganizationBranding
|
||||
{
|
||||
public const DEFAULT_LOGO = 'images/logo/ladillqueue-logo.svg';
|
||||
|
||||
public static function logoUrl(Organization $organization): string
|
||||
{
|
||||
if ($organization->logo_path && Storage::disk('public')->exists($organization->logo_path)) {
|
||||
$version = $organization->updated_at?->getTimestamp() ?? time();
|
||||
|
||||
return Storage::disk('public')->url($organization->logo_path).'?v='.$version;
|
||||
}
|
||||
|
||||
$path = public_path(self::DEFAULT_LOGO);
|
||||
|
||||
return asset(self::DEFAULT_LOGO).'?v='.(@filemtime($path) ?: '1');
|
||||
}
|
||||
|
||||
public static function logoAlt(Organization $organization): string
|
||||
{
|
||||
return $organization->logo_path ? $organization->name : 'Ladill Queue';
|
||||
}
|
||||
|
||||
public static function hasCustomLogo(Organization $organization): bool
|
||||
{
|
||||
return $organization->logo_path !== null
|
||||
&& Storage::disk('public')->exists($organization->logo_path);
|
||||
}
|
||||
|
||||
public static function storeLogo(Organization $organization, UploadedFile $file): string
|
||||
{
|
||||
self::deleteStoredLogo($organization);
|
||||
|
||||
return $file->store('queue/organizations/'.$organization->id, 'public');
|
||||
}
|
||||
|
||||
public static function deleteStoredLogo(Organization $organization): void
|
||||
{
|
||||
if ($organization->logo_path) {
|
||||
Storage::disk('public')->delete($organization->logo_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class UserProfileMenu
|
||||
{
|
||||
/**
|
||||
* Standard signed-in profile links for Ladill product apps.
|
||||
* Not used by Ladill Mail (mail.ladill.com) — that app keeps a mailbox-specific menu.
|
||||
*
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public static function items(?Authenticatable $user = null): array
|
||||
{
|
||||
$user ??= auth()->user();
|
||||
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ([
|
||||
['label' => 'Home', 'path' => '', 'host' => 'home'],
|
||||
['label' => 'Profile', 'path' => 'profile'],
|
||||
['label' => 'Account Settings', 'path' => 'account-settings'],
|
||||
['label' => 'Dashboard', 'path' => 'dashboard'],
|
||||
['label' => 'Billing', 'path' => 'billing'],
|
||||
] as $link) {
|
||||
$href = self::platformUrl($link['host'] ?? 'account', $link['path']);
|
||||
|
||||
if (self::isCurrentMenuLink($link, $href)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'type' => 'link',
|
||||
'label' => $link['label'],
|
||||
'href' => $href,
|
||||
];
|
||||
}
|
||||
|
||||
if (Route::has((string) config('billing.wallet_balance_route', 'wallet.balance'))) {
|
||||
$items[] = ['type' => 'wallet'];
|
||||
}
|
||||
|
||||
if (self::userIsAdmin($user)) {
|
||||
$adminHref = self::platformUrl('account', 'admin');
|
||||
|
||||
if (! self::isCurrentMenuLink(['path' => 'admin', 'host' => 'account'], $adminHref)) {
|
||||
$items[] = [
|
||||
'type' => 'link',
|
||||
'label' => 'Admin',
|
||||
'href' => $adminHref,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (Route::has('logout')) {
|
||||
$items[] = [
|
||||
'type' => 'logout',
|
||||
'label' => 'Logout',
|
||||
'action' => route('logout'),
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function platformUrl(string $host, string $path = ''): string
|
||||
{
|
||||
if ($host === 'home') {
|
||||
if (function_exists('ladill_home_url')) {
|
||||
return ladill_home_url($path);
|
||||
}
|
||||
|
||||
$domain = config('app.home_domain');
|
||||
if (! $domain) {
|
||||
$platform = (string) config('app.platform_domain', parse_url((string) config('app.url', ''), PHP_URL_HOST) ?: '');
|
||||
$domain = $platform !== '' ? 'home.'.$platform : (string) config('app.account_domain');
|
||||
}
|
||||
|
||||
return self::absoluteUrl((string) $domain, $path);
|
||||
}
|
||||
|
||||
if (function_exists('ladill_account_url')) {
|
||||
return ladill_account_url($path);
|
||||
}
|
||||
|
||||
return self::absoluteUrl((string) config('app.account_domain'), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a menu link when the signed-in user is already on that destination.
|
||||
*
|
||||
* @param array{label?: string, path?: string, host?: string} $link
|
||||
*/
|
||||
private static function isCurrentMenuLink(array $link, string $href): bool
|
||||
{
|
||||
if (app()->runningInConsole() && ! app()->runningUnitTests()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request = request();
|
||||
if (! $request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$linkHost = strtolower((string) parse_url($href, PHP_URL_HOST));
|
||||
$currentHost = strtolower($request->getHost());
|
||||
|
||||
if ($linkHost === '' || $linkHost !== $currentHost) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentPath = trim($request->getPathInfo(), '/');
|
||||
$targetPath = trim((string) parse_url($href, PHP_URL_PATH), '/');
|
||||
|
||||
if (($link['host'] ?? 'account') === 'home') {
|
||||
return $currentPath === '';
|
||||
}
|
||||
|
||||
if (($link['path'] ?? '') === 'dashboard') {
|
||||
return in_array($currentPath, ['dashboard', 'account'], true)
|
||||
|| ($targetPath !== '' && self::pathMatchesSection($currentPath, $targetPath));
|
||||
}
|
||||
|
||||
return self::pathMatchesSection($currentPath, $targetPath);
|
||||
}
|
||||
|
||||
private static function pathMatchesSection(string $currentPath, string $targetPath): bool
|
||||
{
|
||||
if ($targetPath === '') {
|
||||
return $currentPath === '';
|
||||
}
|
||||
|
||||
return $currentPath === $targetPath
|
||||
|| str_starts_with($currentPath, $targetPath.'/');
|
||||
}
|
||||
|
||||
private static function absoluteUrl(string $domain, string $path = ''): string
|
||||
{
|
||||
$base = 'https://'.trim($domain, '/');
|
||||
|
||||
if ($path === '') {
|
||||
return $base;
|
||||
}
|
||||
|
||||
return $base.'/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
private static function userIsAdmin(Authenticatable $user): bool
|
||||
{
|
||||
if (method_exists($user, 'isAdmin')) {
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
return (bool) ($user->is_admin ?? false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
if (! function_exists('ladill_platform_url')) {
|
||||
/** Absolute URL on the platform root (ladill.com). */
|
||||
function ladill_platform_url(string $path = '/'): string
|
||||
{
|
||||
return 'https://'.Config::get('app.platform_domain', 'ladill.com').'/'.ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ladill_account_url')) {
|
||||
/** Absolute URL on the unified account portal (account.ladill.com). */
|
||||
function ladill_account_url(string $path = '/'): string
|
||||
{
|
||||
return 'https://'.Config::get('app.account_domain', 'account.ladill.com').'/'.ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ladill_home_url')) {
|
||||
/** Absolute URL on the signed-in product hub (home.ladill.com). */
|
||||
function ladill_home_url(string $path = '/'): string
|
||||
{
|
||||
$platform = (string) Config::get('app.platform_domain', 'ladill.com');
|
||||
|
||||
return 'https://home.'.$platform.'/'.ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ladill_auth_url')) {
|
||||
/** Absolute URL on the identity provider (auth.ladill.com). */
|
||||
function ladill_auth_url(string $path = '/'): string
|
||||
{
|
||||
return 'https://'.Config::get('app.auth_domain', 'auth.ladill.com').'/'.ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('crm_money')) {
|
||||
/** Format integer minor units as a human currency string (e.g. 12500 → GHS 125.00). */
|
||||
function crm_money(?int $minor, ?string $currency = null): string
|
||||
{
|
||||
$currency = $currency ?: Config::get('crm.default_currency', 'GHS');
|
||||
|
||||
return $currency.' '.number_format(((int) $minor) / 100, 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user