Auto commit: 2025-11-25T00:27:52.092Z
This commit is contained in:
parent
c7c48f13ae
commit
f04bc2cc23
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[compose.yaml]
|
||||
indent_size = 4
|
||||
65
.env.example
Normal file
65
.env.example
Normal file
@ -0,0 +1,65 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@ -1,3 +1,24 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
|
||||
59
README.md
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## About Laravel
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
|
||||
## Learning Laravel
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Redberry](https://redberry.international/laravel-development)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
@ -1,493 +0,0 @@
|
||||
<?php
|
||||
// LocalAIApi — proxy client for the Responses API.
|
||||
// Usage (async: auto-polls status until ready):
|
||||
// require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||
// $response = LocalAIApi::createResponse([
|
||||
// 'input' => [
|
||||
// ['role' => 'system', 'content' => 'You are a helpful assistant.'],
|
||||
// ['role' => 'user', 'content' => 'Tell me a bedtime story.'],
|
||||
// ],
|
||||
// ]);
|
||||
// if (!empty($response['success'])) {
|
||||
// // response['data'] contains full payload, e.g.:
|
||||
// // {
|
||||
// // "id": "resp_xxx",
|
||||
// // "status": "completed",
|
||||
// // "output": [
|
||||
// // {"type": "reasoning", "summary": []},
|
||||
// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]}
|
||||
// // ]
|
||||
// // }
|
||||
// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...)
|
||||
// }
|
||||
// Poll settings override:
|
||||
// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]);
|
||||
|
||||
class LocalAIApi
|
||||
{
|
||||
/** @var array<string,mixed>|null */
|
||||
private static ?array $configCache = null;
|
||||
|
||||
/**
|
||||
* Signature compatible with the OpenAI Responses API.
|
||||
*
|
||||
* @param array<string,mixed> $params Request body (model, input, text, reasoning, metadata, etc.).
|
||||
* @param array<string,mixed> $options Extra options (timeout, verify_tls, headers, path, project_uuid).
|
||||
* @return array{
|
||||
* success:bool,
|
||||
* status?:int,
|
||||
* data?:mixed,
|
||||
* error?:string,
|
||||
* response?:mixed,
|
||||
* message?:string
|
||||
* }
|
||||
*/
|
||||
public static function createResponse(array $params, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
$payload = $params;
|
||||
|
||||
if (empty($payload['input']) || !is_array($payload['input'])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'input_missing',
|
||||
'message' => 'Parameter "input" is required and must be an array.',
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($payload['model']) || $payload['model'] === '') {
|
||||
$payload['model'] = $cfg['default_model'];
|
||||
}
|
||||
|
||||
$initial = self::request($options['path'] ?? null, $payload, $options);
|
||||
if (empty($initial['success'])) {
|
||||
return $initial;
|
||||
}
|
||||
|
||||
// Async flow: if backend returns ai_request_id, poll status until ready
|
||||
$data = $initial['data'] ?? null;
|
||||
if (is_array($data) && isset($data['ai_request_id'])) {
|
||||
$aiRequestId = $data['ai_request_id'];
|
||||
$pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds
|
||||
$pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds
|
||||
return self::awaitResponse($aiRequestId, [
|
||||
'timeout' => $pollTimeout,
|
||||
'interval' => $pollInterval,
|
||||
'headers' => $options['headers'] ?? [],
|
||||
'timeout_per_call' => $options['timeout'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snake_case alias for createResponse (matches the provided example).
|
||||
*
|
||||
* @param array<string,mixed> $params
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function create_response(array $params, array $options = []): array
|
||||
{
|
||||
return self::createResponse($params, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a raw request to the AI proxy.
|
||||
*
|
||||
* @param string $path Endpoint (may be an absolute URL).
|
||||
* @param array<string,mixed> $payload JSON payload.
|
||||
* @param array<string,mixed> $options Additional request options.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function request(?string $path = null, array $payload = [], array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
|
||||
$projectUuid = $cfg['project_uuid'];
|
||||
if (empty($projectUuid)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_uuid_missing',
|
||||
'message' => 'PROJECT_UUID is not defined; aborting AI request.',
|
||||
];
|
||||
}
|
||||
|
||||
$defaultPath = $cfg['responses_path'] ?? null;
|
||||
$resolvedPath = $path ?? ($options['path'] ?? $defaultPath);
|
||||
if (empty($resolvedPath)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_id_missing',
|
||||
'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.',
|
||||
];
|
||||
}
|
||||
|
||||
$url = self::buildUrl($resolvedPath, $cfg['base_url']);
|
||||
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
|
||||
if ($timeout <= 0) {
|
||||
$timeout = 30;
|
||||
}
|
||||
|
||||
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
|
||||
$verifyTls = array_key_exists('verify_tls', $options)
|
||||
? (bool) $options['verify_tls']
|
||||
: $baseVerifyTls;
|
||||
|
||||
$projectHeader = $cfg['project_header'];
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
];
|
||||
$headers[] = $projectHeader . ': ' . $projectUuid;
|
||||
if (!empty($options['headers']) && is_array($options['headers'])) {
|
||||
foreach ($options['headers'] as $header) {
|
||||
if (is_string($header) && $header !== '') {
|
||||
$headers[] = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) {
|
||||
$payload['project_uuid'] = $projectUuid;
|
||||
}
|
||||
|
||||
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
if ($body === false) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'json_encode_failed',
|
||||
'message' => 'Failed to encode request body to JSON.',
|
||||
];
|
||||
}
|
||||
|
||||
return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll AI request status until ready or timeout.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function awaitResponse($aiRequestId, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds
|
||||
$interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds
|
||||
if ($interval <= 0) {
|
||||
$interval = 5;
|
||||
}
|
||||
$perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null;
|
||||
|
||||
$deadline = time() + max($timeout, $interval);
|
||||
$headers = $options['headers'] ?? [];
|
||||
|
||||
while (true) {
|
||||
$statusResp = self::fetchStatus($aiRequestId, [
|
||||
'headers' => $headers,
|
||||
'timeout' => $perCallTimeout,
|
||||
]);
|
||||
if (!empty($statusResp['success'])) {
|
||||
$data = $statusResp['data'] ?? [];
|
||||
if (is_array($data)) {
|
||||
$statusValue = $data['status'] ?? null;
|
||||
if ($statusValue === 'success') {
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => 200,
|
||||
'data' => $data['response'] ?? $data,
|
||||
];
|
||||
}
|
||||
if ($statusValue === 'failed') {
|
||||
return [
|
||||
'success' => false,
|
||||
'status' => 500,
|
||||
'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed',
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return $statusResp;
|
||||
}
|
||||
|
||||
if (time() >= $deadline) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'timeout',
|
||||
'message' => 'Timed out waiting for AI response.',
|
||||
];
|
||||
}
|
||||
sleep($interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch status for queued AI request.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function fetchStatus($aiRequestId, array $options = []): array
|
||||
{
|
||||
$cfg = self::config();
|
||||
$projectUuid = $cfg['project_uuid'];
|
||||
if (empty($projectUuid)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'project_uuid_missing',
|
||||
'message' => 'PROJECT_UUID is not defined; aborting status check.',
|
||||
];
|
||||
}
|
||||
|
||||
$statusPath = self::resolveStatusPath($aiRequestId, $cfg);
|
||||
$url = self::buildUrl($statusPath, $cfg['base_url']);
|
||||
|
||||
$baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30;
|
||||
$timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout;
|
||||
if ($timeout <= 0) {
|
||||
$timeout = 30;
|
||||
}
|
||||
|
||||
$baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true;
|
||||
$verifyTls = array_key_exists('verify_tls', $options)
|
||||
? (bool) $options['verify_tls']
|
||||
: $baseVerifyTls;
|
||||
|
||||
$projectHeader = $cfg['project_header'];
|
||||
$headers = [
|
||||
'Accept: application/json',
|
||||
$projectHeader . ': ' . $projectUuid,
|
||||
];
|
||||
if (!empty($options['headers']) && is_array($options['headers'])) {
|
||||
foreach ($options['headers'] as $header) {
|
||||
if (is_string($header) && $header !== '') {
|
||||
$headers[] = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::sendCurl($url, 'GET', null, $headers, $timeout, $verifyTls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from a Responses API payload.
|
||||
*
|
||||
* @param array<string,mixed> $response Result of LocalAIApi::createResponse|request.
|
||||
* @return string
|
||||
*/
|
||||
public static function extractText(array $response): string
|
||||
{
|
||||
$payload = $response['data'] ?? $response;
|
||||
if (!is_array($payload)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!empty($payload['output']) && is_array($payload['output'])) {
|
||||
$combined = '';
|
||||
foreach ($payload['output'] as $item) {
|
||||
if (!isset($item['content']) || !is_array($item['content'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($item['content'] as $block) {
|
||||
if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) {
|
||||
$combined .= $block['text'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($combined !== '') {
|
||||
return $combined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($payload['choices'][0]['message']['content'])) {
|
||||
return (string) $payload['choices'][0]['message']['content'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to decode JSON emitted by the model (handles markdown fences).
|
||||
*
|
||||
* @param array<string,mixed> $response
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
public static function decodeJsonFromResponse(array $response): ?array
|
||||
{
|
||||
$text = self::extractText($response);
|
||||
if ($text === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($text, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
$stripped = preg_replace('/^```json|```$/m', '', trim($text));
|
||||
if ($stripped !== null && $stripped !== $text) {
|
||||
$decoded = json_decode($stripped, true);
|
||||
if (is_array($decoded)) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from ai/config.php.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function config(): array
|
||||
{
|
||||
if (self::$configCache === null) {
|
||||
$configPath = __DIR__ . '/config.php';
|
||||
if (!file_exists($configPath)) {
|
||||
throw new RuntimeException('AI config file not found: ai/config.php');
|
||||
}
|
||||
$cfg = require $configPath;
|
||||
if (!is_array($cfg)) {
|
||||
throw new RuntimeException('Invalid AI config format: expected array');
|
||||
}
|
||||
self::$configCache = $cfg;
|
||||
}
|
||||
|
||||
return self::$configCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an absolute URL from base_url and a path.
|
||||
*/
|
||||
private static function buildUrl(string $path, string $baseUrl): string
|
||||
{
|
||||
$trimmed = trim($path);
|
||||
if ($trimmed === '') {
|
||||
return $baseUrl;
|
||||
}
|
||||
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
|
||||
return $trimmed;
|
||||
}
|
||||
if ($trimmed[0] === '/') {
|
||||
return $baseUrl . $trimmed;
|
||||
}
|
||||
return $baseUrl . '/' . $trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve status path based on configured responses_path and ai_request_id.
|
||||
*
|
||||
* @param int|string $aiRequestId
|
||||
* @param array<string,mixed> $cfg
|
||||
* @return string
|
||||
*/
|
||||
private static function resolveStatusPath($aiRequestId, array $cfg): string
|
||||
{
|
||||
$basePath = $cfg['responses_path'] ?? '';
|
||||
$trimmed = rtrim($basePath, '/');
|
||||
if ($trimmed === '') {
|
||||
return '/ai-request/' . rawurlencode((string)$aiRequestId) . '/status';
|
||||
}
|
||||
if (substr($trimmed, -11) !== '/ai-request') {
|
||||
$trimmed .= '/ai-request';
|
||||
}
|
||||
return $trimmed . '/' . rawurlencode((string)$aiRequestId) . '/status';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared CURL sender for GET/POST requests.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param string|null $body
|
||||
* @param array<int,string> $headers
|
||||
* @param int $timeout
|
||||
* @param bool $verifyTls
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array
|
||||
{
|
||||
if (!function_exists('curl_init')) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'curl_missing',
|
||||
'message' => 'PHP cURL extension is missing. Install or enable it on the VM.',
|
||||
];
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0);
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, false);
|
||||
|
||||
$upper = strtoupper($method);
|
||||
if ($upper === 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? '');
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
}
|
||||
|
||||
$responseBody = curl_exec($ch);
|
||||
if ($responseBody === false) {
|
||||
$error = curl_error($ch) ?: 'Unknown cURL error';
|
||||
curl_close($ch);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'curl_error',
|
||||
'message' => $error,
|
||||
];
|
||||
}
|
||||
|
||||
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$decoded = null;
|
||||
if ($responseBody !== '' && $responseBody !== null) {
|
||||
$decoded = json_decode($responseBody, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$decoded = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($status >= 200 && $status < 300) {
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => $status,
|
||||
'data' => $decoded ?? $responseBody,
|
||||
];
|
||||
}
|
||||
|
||||
$errorMessage = 'AI proxy request failed';
|
||||
if (is_array($decoded)) {
|
||||
$errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage;
|
||||
} elseif (is_string($responseBody) && $responseBody !== '') {
|
||||
$errorMessage = $responseBody;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'status' => $status,
|
||||
'error' => $errorMessage,
|
||||
'response' => $decoded ?? $responseBody,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy alias for backward compatibility with the previous class name.
|
||||
if (!class_exists('OpenAIService')) {
|
||||
class_alias(LocalAIApi::class, 'OpenAIService');
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
// OpenAI proxy configuration (workspace scope).
|
||||
// Reads values from environment variables or executor/.env.
|
||||
|
||||
$projectUuid = getenv('PROJECT_UUID');
|
||||
$projectId = getenv('PROJECT_ID');
|
||||
|
||||
if (
|
||||
($projectUuid === false || $projectUuid === null || $projectUuid === '') ||
|
||||
($projectId === false || $projectId === null || $projectId === '')
|
||||
) {
|
||||
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||
if ($envPath && is_readable($envPath)) {
|
||||
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($line, '=')) {
|
||||
continue;
|
||||
}
|
||||
[$key, $value] = array_map('trim', explode('=', $line, 2));
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$value = trim($value, "\"' ");
|
||||
if (getenv($key) === false || getenv($key) === '') {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
$projectUuid = getenv('PROJECT_UUID');
|
||||
$projectId = getenv('PROJECT_ID');
|
||||
}
|
||||
}
|
||||
|
||||
$projectUuid = ($projectUuid === false) ? null : $projectUuid;
|
||||
$projectId = ($projectId === false) ? null : $projectId;
|
||||
|
||||
$baseUrl = 'https://flatlogic.com';
|
||||
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
|
||||
|
||||
return [
|
||||
'base_url' => $baseUrl,
|
||||
'responses_path' => $responsesPath,
|
||||
'project_id' => $projectId,
|
||||
'project_uuid' => $projectUuid,
|
||||
'project_header' => 'project-uuid',
|
||||
'default_model' => 'gpt-5-mini',
|
||||
'timeout' => 30,
|
||||
'verify_tls' => true,
|
||||
];
|
||||
49
api.php
49
api.php
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once 'db/config.php';
|
||||
|
||||
$action = $_GET['action'] ?? 'get_current_locations';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
switch ($action) {
|
||||
case 'get_route_history':
|
||||
$id_taxi = $_GET['id_taxi'] ?? null;
|
||||
if (!$id_taxi) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Se requiere id_taxi']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT latitud, longitud FROM localizacion_historico WHERE id_taxi = ? ORDER BY fecha_registro ASC"
|
||||
);
|
||||
$stmt->execute([$id_taxi]);
|
||||
$history = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($history);
|
||||
break;
|
||||
|
||||
case 'get_current_locations':
|
||||
default:
|
||||
$stmt = $pdo->query(
|
||||
"SELECT
|
||||
t.id, t.nombre, t.licencia, t.matricula, t.municipio,
|
||||
t.ultima_localizacion_lat as latitud, t.ultima_localizacion_lng as longitud,
|
||||
(SELECT COUNT(*) FROM documents WHERE id_conductor = t.id) as num_documentos,
|
||||
(SELECT COUNT(*) FROM citas WHERE id_conductor = t.id) as num_citas,
|
||||
(SELECT COUNT(*) FROM consultas WHERE id_conductor = t.id) as num_consultas,
|
||||
(SELECT COUNT(*) FROM localizacion_historico WHERE id_taxi = t.id) as num_ubicaciones
|
||||
FROM taxis t
|
||||
GROUP BY t.id
|
||||
ORDER BY t.nombre ASC"
|
||||
);
|
||||
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo json_encode($locations);
|
||||
break;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error de base de datos: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
64
app/Http/Controllers/CitaController.php
Normal file
64
app/Http/Controllers/CitaController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CitaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Http/Controllers/ConsultaController.php
Normal file
64
app/Http/Controllers/ConsultaController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ConsultaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
64
app/Http/Controllers/DepartamentoController.php
Normal file
64
app/Http/Controllers/DepartamentoController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DepartamentoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Http/Controllers/DocumentoController.php
Normal file
64
app/Http/Controllers/DocumentoController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DocumentoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Http/Controllers/LocalizacionHistoricoController.php
Normal file
64
app/Http/Controllers/LocalizacionHistoricoController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LocalizacionHistoricoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
74
app/Http/Controllers/LocalizacionTaxiController.php
Normal file
74
app/Http/Controllers/LocalizacionTaxiController.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LocalizacionTaxi;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LocalizacionTaxiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('localizacion_taxi.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all taxi locations.
|
||||
*/
|
||||
public function getLocations()
|
||||
{
|
||||
$locations = LocalizacionTaxi::all();
|
||||
return response()->json($locations);
|
||||
}
|
||||
}
|
||||
64
app/Http/Controllers/TaxiController.php
Normal file
64
app/Http/Controllers/TaxiController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaxiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
10
app/Models/Cita.php
Normal file
10
app/Models/Cita.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Cita extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/Consulta.php
Normal file
10
app/Models/Consulta.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Consulta extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/Departamento.php
Normal file
10
app/Models/Departamento.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Departamento extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/Documento.php
Normal file
10
app/Models/Documento.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Documento extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/LocalizacionHistorico.php
Normal file
10
app/Models/LocalizacionHistorico.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LocalizacionHistorico extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/LocalizacionTaxi.php
Normal file
10
app/Models/LocalizacionTaxi.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LocalizacionTaxi extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
10
app/Models/Taxi.php
Normal file
10
app/Models/Taxi.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Taxi extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
48
app/Models/User.php
Normal file
48
app/Models/User.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
18
artisan
Executable file
18
artisan
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
$status = $app->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
18
bootstrap/app.php
Normal file
18
bootstrap/app.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
//
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
})->create();
|
||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
5
bootstrap/providers.php
Normal file
5
bootstrap/providers.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
];
|
||||
262
citas.php
262
citas.php
@ -1,262 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
$message = '';
|
||||
$message_type = '';
|
||||
|
||||
// --- DB Schema and Data Fetching ---
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Main schema definition
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS citas (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
start_event DATETIME NOT NULL,
|
||||
end_event DATETIME NOT NULL,
|
||||
id_departamento INT NOT NULL,
|
||||
lugar VARCHAR(255),
|
||||
usuarios TEXT,
|
||||
estado VARCHAR(50) DEFAULT 'Pendiente',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_departamento) REFERENCES departamentos(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Add 'title' column if it doesn't exist
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM citas LIKE 'title'");
|
||||
if ($stmt->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE citas ADD COLUMN title VARCHAR(255) NOT NULL AFTER id;");
|
||||
}
|
||||
|
||||
// Add 'id_conductor' column if it doesn't exist
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM citas LIKE 'id_conductor'");
|
||||
if ($stmt->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE citas ADD COLUMN id_conductor INT NULL AFTER id_departamento, ADD FOREIGN KEY (id_conductor) REFERENCES taxis(id) ON DELETE SET NULL;");
|
||||
}
|
||||
|
||||
// Fetch departments and conductors
|
||||
$departamentos = $pdo->query("SELECT id, nombre FROM departamentos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$conductores = $pdo->query("SELECT id, matricula FROM taxis ORDER BY matricula")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Get selected filters
|
||||
$selected_depto_id = $_GET['id_departamento'] ?? null;
|
||||
$selected_conductor_id = $_GET['id_conductor'] ?? null;
|
||||
|
||||
// Handle form submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_cita'])) {
|
||||
$title = $_POST['title'];
|
||||
$fecha = $_POST['fecha'];
|
||||
$hora_inicio = $_POST['hora_inicio'];
|
||||
$hora_fin = $_POST['hora_fin'];
|
||||
$id_departamento = $_POST['id_departamento'];
|
||||
$id_conductor = $_POST['id_conductor'] ?: null;
|
||||
$lugar = trim($_POST['lugar']);
|
||||
$usuarios = trim($_POST['usuarios']);
|
||||
$estado = $_POST['estado'];
|
||||
|
||||
if (!empty($title) && !empty($fecha) && !empty($hora_inicio) && !empty($hora_fin) && !empty($id_departamento)) {
|
||||
try {
|
||||
$start_event_dt = new DateTime($fecha . ' ' . $hora_inicio);
|
||||
$end_event_dt = new DateTime($fecha . ' ' . $hora_fin);
|
||||
$start_event = $start_event_dt->format('Y-m-d H:i:s');
|
||||
$end_event = $end_event_dt->format('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO citas (title, start_event, end_event, id_departamento, id_conductor, lugar, usuarios, estado) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$title, $start_event, $end_event, $id_departamento, $id_conductor, $lugar, $usuarios, $estado]);
|
||||
$message = '<div class="alert alert-success">Cita añadida con éxito.</div>';
|
||||
} catch (Exception $e) {
|
||||
$message = '<div class="alert alert-danger">Formato de fecha u hora inválido.</div>';
|
||||
}
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Título, fecha, horas y departamento son obligatorios.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch citas for the calendar and list
|
||||
$events = [];
|
||||
$citas_list = [];
|
||||
|
||||
$sql = "SELECT c.id, c.title, c.start_event as start, c.end_event as end, c.estado, c.lugar, c.usuarios, c.created_at, t.matricula as conductor_nombre
|
||||
FROM citas c
|
||||
LEFT JOIN taxis t ON c.id_conductor = t.id
|
||||
WHERE 1=1";
|
||||
$params = [];
|
||||
if ($selected_depto_id) {
|
||||
$sql .= " AND c.id_departamento = ?";
|
||||
$params[] = $selected_depto_id;
|
||||
}
|
||||
if ($selected_conductor_id) {
|
||||
$sql .= " AND c.id_conductor = ?";
|
||||
$params[] = $selected_conductor_id;
|
||||
}
|
||||
$sql .= " ORDER BY c.start_event DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$citas_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Format for FullCalendar
|
||||
foreach($citas_list as $cita) {
|
||||
$events[] = [
|
||||
'title' => $cita['title'],
|
||||
'start' => $cita['start'],
|
||||
'end' => $cita['end'],
|
||||
'extendedProps' => [
|
||||
'estado' => $cita['estado'],
|
||||
'conductor' => $cita['conductor_nombre'] ?? 'N/A'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message = "Error de base de datos: " . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- FullCalendar CSS -->
|
||||
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.css' rel='stylesheet' />
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Calendario de Citas</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Citas</li>
|
||||
</ol>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-info alert-dismissible fade show" role="alert">
|
||||
<?php echo $message; ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-filter me-1"></i>Filtros</div>
|
||||
<div class="card-body">
|
||||
<form action="citas.php" method="GET" id="filter-form">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<select class="form-select" name="id_departamento" onchange="this.form.submit()">
|
||||
<option value="">Seleccione un departamento</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>" <?php echo ($depto['id'] == $selected_depto_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<select class="form-select" name="id_conductor" onchange="this.form.submit()">
|
||||
<option value="">Todos los conductores</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>" <?php echo ($conductor['id'] == $selected_conductor_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($conductor['matricula']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($selected_depto_id || $selected_conductor_id): ?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div id='calendar'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-list me-1"></i>Listado de Citas</div>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead><tr><th>Título</th><th>Conductor</th><th>Inicio</th><th>Fin</th><th>Estado</th><th>Lugar</th><th>Usuarios</th></tr></thead>
|
||||
<tbody>
|
||||
<?php if(empty($citas_list)): ?>
|
||||
<tr><td colspan="7" class="text-center">No hay citas para los filtros seleccionados.</td></tr>
|
||||
<?php else: foreach($citas_list as $cita): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($cita['title']); ?></td>
|
||||
<td><?php echo htmlspecialchars($cita['conductor_nombre'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo date('d/m/Y H:i', strtotime($cita['start'])); ?></td>
|
||||
<td><?php echo date('d/m/Y H:i', strtotime($cita['end'])); ?></td>
|
||||
<td><?php echo htmlspecialchars($cita['estado']); ?></td>
|
||||
<td><?php echo htmlspecialchars($cita['lugar']); ?></td>
|
||||
<td><?php echo htmlspecialchars($cita['usuarios']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info">Por favor, seleccione un departamento o conductor para ver el calendario y las citas.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-plus me-1"></i>Añadir Nueva Cita</div>
|
||||
<div class="card-body">
|
||||
<form action="citas.php?<?php echo http_build_query($_GET); ?>" method="POST">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" type="text" name="title" placeholder="Título" required />
|
||||
<label>Título de la cita</label>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4"><div class="form-floating"><input class="form-control" type="date" name="fecha" required /><label>Fecha</label></div></div>
|
||||
<div class="col-md-4"><div class="form-floating"><input class="form-control" type="time" name="hora_inicio" required /><label>Hora Inicio</label></div></div>
|
||||
<div class="col-md-4"><div class="form-floating"><input class="form-control" type="time" name="hora_fin" required /><label>Hora Fin</label></div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="form-floating"><select class="form-select" name="id_departamento" required>
|
||||
<option value="">Seleccione departamento</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>" <?php echo ($depto['id'] == $selected_depto_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select><label>Departamento</label></div></div>
|
||||
<div class="col-md-6"><div class="form-floating"><select class="form-select" name="id_conductor">
|
||||
<option value="">Seleccione un conductor (Opcional)</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>" <?php echo ($conductor['id'] == $selected_conductor_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($conductor['matricula']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select><label>Conductor</label></div></div>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" type="text" name="lugar" placeholder="Lugar" />
|
||||
<label>Lugar</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<textarea class="form-control" name="usuarios" placeholder="Usuarios" style="height: 80px;"></textarea>
|
||||
<label>Usuarios</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3"><select class="form-select" name="estado">
|
||||
<option value="Pendiente">Pendiente</option>
|
||||
<option value="Confirmada">Confirmada</option>
|
||||
<option value="Cancelada">Cancelada</option>
|
||||
</select><label>Estado</label></div>
|
||||
<div class="d-grid"><button type="submit" name="add_cita" class="btn btn-primary">Añadir Cita</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FullCalendar JS -->
|
||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.min.js'></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php if ($selected_depto_id || $selected_conductor_id): ?>
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth',
|
||||
locale: 'es',
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
},
|
||||
events: <?php echo json_encode($events); ?>
|
||||
});
|
||||
calendar.render();
|
||||
<?php endif; ?>
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
86
composer.json
Normal file
86
composer.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^11.5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"setup": [
|
||||
"composer install",
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
|
||||
"@php artisan key:generate",
|
||||
"@php artisan migrate --force",
|
||||
"npm install",
|
||||
"npm run build"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
||||
],
|
||||
"test": [
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php artisan test"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
8366
composer.lock
generated
Normal file
8366
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
config/app.php
Normal file
126
config/app.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application, which will be used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
115
config/auth.php
Normal file
115
config/auth.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| providers to represent the model / table. These providers may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the number of seconds before a password confirmation
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
117
config/cache.php
Normal file
117
config/cache.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache store that will be used by the
|
||||
| framework. This connection is utilized if another isn't explicitly
|
||||
| specified when running a cache operation inside the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "array", "database", "file", "memcached",
|
||||
| "redis", "dynamodb", "octane",
|
||||
| "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'stores' => [
|
||||
'database',
|
||||
'array',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||
| stores, there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
|
||||
|
||||
];
|
||||
183
config/database.php
Normal file
183
config/database.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for database operations. This is
|
||||
| the connection which will be utilized unless another connection
|
||||
| is explicitly specified when you execute a query / statement.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below are all of the database connections defined for your application.
|
||||
| An example configuration is provided for each database system which
|
||||
| is supported by Laravel. You're free to add / remove connections.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => null,
|
||||
'journal_mode' => null,
|
||||
'synchronous' => null,
|
||||
'transaction_mode' => 'DEFERRED',
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
|
||||
'persistent' => env('REDIS_PERSISTENT', false),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
80
config/filesystems.php
Normal file
80
config/filesystems.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below you may configure as many filesystem disks as necessary, and you
|
||||
| may even configure multiple disks for the same driver. Examples for
|
||||
| most supported storage drivers are configured here for reference.
|
||||
|
|
||||
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
132
config/logging.php
Normal file
132
config/logging.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Monolog\Processor\PsrLogMessageProcessor;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'handler_with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
118
config/mail.php
Normal file
118
config/mail.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send all email
|
||||
| messages unless another mailer is explicitly specified when sending
|
||||
| the message. All additional mailers can be configured within the
|
||||
| "mailers" array. Examples of each type of mailer are provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||
| when delivering an email. You may specify which one you're using for
|
||||
| your mailers below. You may also add additional mailers if needed.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "resend", "log", "array",
|
||||
| "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'transport' => 'resend',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all emails sent by your application to be sent from
|
||||
| the same address. Here you may specify a name and address that is
|
||||
| used globally for all emails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
];
|
||||
129
config/queue.php
Normal file
129
config/queue.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue supports a variety of backends via a single, unified
|
||||
| API, giving you convenient access to each backend using identical
|
||||
| syntax for each. The default queue connection is defined below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection options for every queue backend
|
||||
| used by your application. An example configuration is provided for
|
||||
| each backend supported by Laravel. You're also free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
|
||||
| "deferred", "background", "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||
'queue' => env('DB_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'deferred' => [
|
||||
'driver' => 'deferred',
|
||||
],
|
||||
|
||||
'background' => [
|
||||
'driver' => 'background',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'connections' => [
|
||||
'database',
|
||||
'deferred',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Batching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following options configure the database and table that store job
|
||||
| batching information. These options can be updated to any database
|
||||
| connection and table which has been defined by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control how and where failed jobs are stored. Laravel ships with
|
||||
| support for storing failed jobs in a simple file or in a database.
|
||||
|
|
||||
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
38
config/services.php
Normal file
38
config/services.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'postmark' => [
|
||||
'key' => env('POSTMARK_API_KEY'),
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'key' => env('RESEND_API_KEY'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'notifications' => [
|
||||
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
217
config/session.php
Normal file
217
config/session.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines the default session driver that is utilized for
|
||||
| incoming requests. Laravel supports a variety of storage options to
|
||||
| persist session data. Database storage is a great default choice.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "memcached",
|
||||
| "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the "file" session driver, the session files are placed
|
||||
| on disk. The default storage location is defined here; however, you
|
||||
| are free to provide another location where they should be stored.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table to
|
||||
| be used to store sessions. Of course, a sensible default is defined
|
||||
| for you; however, you're welcome to change this to another table.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using one of the framework's cache driven session backends, you may
|
||||
| define the cache store which should be used to store the session data
|
||||
| between requests. This must match one of your defined cache stores.
|
||||
|
|
||||
| Affects: "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the session cookie that is created by
|
||||
| the framework. Typically, you should not need to change this value
|
||||
| since doing so does not grant a meaningful security improvement.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the domain and subdomains the session cookie is
|
||||
| available to. By default, the cookie will be available to the root
|
||||
| domain and all subdomains. Typically, this shouldn't be changed.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" to permit secure cross-site requests.
|
||||
|
|
||||
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||
|
||||
];
|
||||
194
consultas.php
194
consultas.php
@ -1,194 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
$message = '';
|
||||
$message_type = '';
|
||||
|
||||
// --- DB Schema and Data Fetching ---
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Create/update consultas table
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS consultas (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_conductor INT NOT NULL,
|
||||
id_departamento INT NOT NULL,
|
||||
asunto VARCHAR(255) NOT NULL,
|
||||
resultado TEXT,
|
||||
status VARCHAR(50) DEFAULT 'Pendiente',
|
||||
fecha_consulta TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_conductor) REFERENCES taxis(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (id_departamento) REFERENCES departamentos(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Fetch departments for dropdowns
|
||||
$departamentos = $pdo->query("SELECT id, nombre FROM departamentos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch taxis/conductores for dropdowns
|
||||
$conductores = $pdo->query("SELECT id, matricula FROM taxis ORDER BY matricula")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Get selected department for filtering
|
||||
$selected_depto_id = $_GET['id_departamento'] ?? null;
|
||||
|
||||
// Handle form submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_consulta'])) {
|
||||
$id_conductor = $_POST['id_conductor'];
|
||||
$id_departamento = $_POST['id_departamento'];
|
||||
$asunto = trim($_POST['asunto']);
|
||||
$resultado = trim($_POST['resultado']);
|
||||
$status = $_POST['status'];
|
||||
|
||||
if (!empty($id_conductor) && !empty($id_departamento) && !empty($asunto)) {
|
||||
$stmt = $pdo->prepare("INSERT INTO consultas (id_conductor, id_departamento, asunto, resultado, status) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$id_conductor, $id_departamento, $asunto, $resultado, $status]);
|
||||
$message = '<div class="alert alert-success">Consulta añadida con éxito.</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Conductor, departamento y asunto son obligatorios.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch consultations
|
||||
$filter_status = $_GET['status'] ?? null;
|
||||
$sql = "SELECT c.id, t.matricula as conductor_nombre, dep.nombre as departamento_nombre, c.asunto, c.resultado, c.status, c.fecha_consulta
|
||||
FROM consultas c
|
||||
JOIN taxis t ON c.id_conductor = t.id
|
||||
JOIN departamentos dep ON c.id_departamento = dep.id";
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
if ($filter_status) {
|
||||
$conditions[] = "c.status = :status";
|
||||
$params[':status'] = $filter_status;
|
||||
}
|
||||
if ($selected_depto_id) {
|
||||
$conditions[] = "c.id_departamento = :id_departamento";
|
||||
$params[':id_departamento'] = $selected_depto_id;
|
||||
}
|
||||
|
||||
if (count($conditions) > 0) {
|
||||
$sql .= " WHERE " . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY c.fecha_consulta DESC";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$consultas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message = "Error de base de datos: " . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Consultas</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Consultas</li>
|
||||
</ol>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-<?php echo $message_type ? $message_type : 'info'; ?> alert-dismissible fade show" role="alert">
|
||||
<?php echo $message; ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-filter me-1"></i>Filtro por Departamento</div>
|
||||
<div class="card-body">
|
||||
<form action="consultas.php" method="GET" id="depto-select-form">
|
||||
<div class="input-group">
|
||||
<select class="form-select" name="id_departamento" onchange="this.form.submit()">
|
||||
<option value="">Todos los departamentos</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>" <?php echo ($depto['id'] == $selected_depto_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if($filter_status): ?>
|
||||
<input type="hidden" name="status" value="<?php echo htmlspecialchars($filter_status); ?>" />
|
||||
<?php endif; ?>
|
||||
<a href="consultas.php" class="btn btn-outline-secondary">Limpiar</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-plus me-1"></i>Añadir Nueva Consulta</div>
|
||||
<div class="card-body">
|
||||
<form action="consultas.php" method="POST">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputAsunto" type="text" name="asunto" placeholder="Asunto" required />
|
||||
<label for="inputAsunto">Asunto</label>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select" name="id_conductor" required>
|
||||
<option value="">Seleccione un conductor</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>"><?php echo htmlspecialchars($conductor['matricula']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label>Conductor</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select" name="id_departamento" required>
|
||||
<option value="">Seleccione un departamento</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>"><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label>Departamento</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<textarea class="form-control" name="resultado" placeholder="Resultado" style="height: 100px;"></textarea>
|
||||
<label>Resultado</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select" name="status">
|
||||
<option value="Pendiente">Pendiente</option>
|
||||
<option value="En Progreso">En Progreso</option>
|
||||
<option value="Resuelta">Resuelta</option>
|
||||
</select>
|
||||
<label>Estado</label>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" name="add_consulta" class="btn btn-primary">Añadir Consulta</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="fas fa-table me-1"></i>Lista de Consultas</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<thead><tr><th>Conductor</th><th>Departamento</th><th>Asunto</th><th>Resultado</th><th>Estado</th><th>Fecha</th></tr></thead>
|
||||
<tbody>
|
||||
<?php if (empty($consultas)): ?>
|
||||
<tr><td colspan="6" class="text-center">No hay consultas.</td></tr>
|
||||
<?php else: foreach ($consultas as $consulta): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($consulta['conductor_nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($consulta['departamento_nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($consulta['asunto']); ?></td>
|
||||
<td><?php echo htmlspecialchars($consulta['resultado']); ?></td>
|
||||
<td><?php echo htmlspecialchars($consulta['status']); ?></td>
|
||||
<td><?php echo date("d/m/Y H:i", strtotime($consulta['fecha_consulta'])); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.sqlite*
|
||||
44
database/factories/UserFactory.php
Normal file
44
database/factories/UserFactory.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
31
database/migrations/2025_11_25_001454_create_taxis_table.php
Normal file
31
database/migrations/2025_11_25_001454_create_taxis_table.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('taxis', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('matricula')->unique();
|
||||
$table->string('marca');
|
||||
$table->string('modelo');
|
||||
$table->year('año');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('taxis');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('departamentos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nombre')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('departamentos');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('consultas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('asunto');
|
||||
$table->text('mensaje');
|
||||
$table->boolean('resuelta')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('consultas');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('documentos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nombre');
|
||||
$table->string('path');
|
||||
$table->string('tipo');
|
||||
$table->foreignId('taxi_id')->constrained('taxis')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('documentos');
|
||||
}
|
||||
};
|
||||
30
database/migrations/2025_11_25_001539_create_citas_table.php
Normal file
30
database/migrations/2025_11_25_001539_create_citas_table.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('citas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->dateTime('fecha_hora');
|
||||
$table->string('motivo');
|
||||
$table->foreignId('taxi_id')->constrained('taxis')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('citas');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('localizacion_taxi', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('taxi_id')->unique()->constrained('taxis')->onDelete('cascade');
|
||||
$table->decimal('latitud', 10, 7);
|
||||
$table->decimal('longitud', 10, 7);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('localizacion_taxi');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('localizacion_historico', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('taxi_id')->constrained('taxis')->onDelete('cascade');
|
||||
$table->decimal('latitud', 10, 7);
|
||||
$table->decimal('longitud', 10, 7);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('localizacion_historico');
|
||||
}
|
||||
};
|
||||
25
database/seeders/DatabaseSeeder.php
Normal file
25
database/seeders/DatabaseSeeder.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
use WithoutModelEvents;
|
||||
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// User::factory(10)->create();
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||
define('DB_HOST', '127.0.0.1');
|
||||
define('DB_NAME', 'app_36230');
|
||||
define('DB_USER', 'app_36230');
|
||||
define('DB_PASS', '07a58cd5-db03-4f7a-98cf-9a5bfb898669');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
// --- DB Schema and Logic ---
|
||||
try {
|
||||
$pdo = db();
|
||||
// Create departments table
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS departamentos (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nombre VARCHAR(255) NOT NULL UNIQUE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
|
||||
$message = '';
|
||||
// Handle POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Add new
|
||||
if (isset($_POST['add_departamento'])) {
|
||||
$nombre = trim($_POST['nombre']);
|
||||
if (!empty($nombre)) {
|
||||
$stmt = $pdo->prepare("INSERT INTO departamentos (nombre) VALUES (?)");
|
||||
$stmt->execute([$nombre]);
|
||||
$message = '<div class="alert alert-success">Departamento añadido.</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">El nombre no puede estar vacío.</div>';
|
||||
}
|
||||
}
|
||||
// Update
|
||||
elseif (isset($_POST['update_departamento'])) {
|
||||
$id = $_POST['id'];
|
||||
$nombre = trim($_POST['nombre']);
|
||||
if (!empty($nombre)) {
|
||||
$stmt = $pdo->prepare("UPDATE departamentos SET nombre = ? WHERE id = ?");
|
||||
$stmt->execute([$nombre, $id]);
|
||||
$message = '<div class="alert alert-success">Departamento actualizado.</div>';
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">El nombre no puede estar vacío.</div>';
|
||||
}
|
||||
}
|
||||
// Delete
|
||||
elseif (isset($_POST['delete_departamento'])) {
|
||||
$id = $_POST['id'];
|
||||
$stmt = $pdo->prepare("DELETE FROM departamentos WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$message = '<div class="alert alert-warning">Departamento eliminado.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all departments
|
||||
$departamentos = $pdo->query("SELECT * FROM departamentos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("DB ERROR: " . $e->getMessage());
|
||||
}
|
||||
|
||||
include 'header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gestión de Departamentos</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Departamentos</li>
|
||||
</ol>
|
||||
|
||||
<?php echo $message; ?>
|
||||
|
||||
<div class="row">
|
||||
<!-- Add Department Form -->
|
||||
<div class="col-xl-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-plus me-1"></i>
|
||||
Añadir Nuevo Departamento
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="departamentos.php" method="POST">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputNombre" type="text" name="nombre" placeholder="Nombre del departamento" required />
|
||||
<label for="inputNombre">Nombre del Departamento</label>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" name="add_departamento" class="btn btn-primary">Añadir</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Departments List -->
|
||||
<div class="col-xl-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-building me-1"></i>
|
||||
Listado de Departamentos
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th class="text-end">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($departamentos)): ?>
|
||||
<tr><td colspan="2" class="text-center">No hay departamentos.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($depto['nombre']); ?></td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editModal-<?php echo $depto['id']; ?>">
|
||||
<i class="bi bi-pencil"></i> Editar
|
||||
</button>
|
||||
<form action="departamentos.php" method="POST" class="d-inline" onsubmit="return confirm('¿Estás seguro de que quieres eliminar este departamento?');">
|
||||
<input type="hidden" name="id" value="<?php echo $depto['id']; ?>">
|
||||
<button type="submit" name="delete_departamento" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i> Eliminar
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal-<?php echo $depto['id']; ?>" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Editar Departamento</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="departamentos.php" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="id" value="<?php echo $depto['id']; ?>">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="nombre" value="<?php echo htmlspecialchars($depto['nombre']); ?>" required>
|
||||
<label>Nombre</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="submit" name="update_departamento" class="btn btn-primary">Guardar Cambios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
296
documents.php
296
documents.php
@ -1,296 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
// --- DB Schema Setup ---
|
||||
try {
|
||||
$pdo = db();
|
||||
// Create documents table with new fields
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS documents (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_conductor INT,
|
||||
tipo_documento VARCHAR(255),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(255) NOT NULL,
|
||||
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_conductor) REFERENCES taxis(id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
|
||||
// Add columns if they don't exist (for existing tables)
|
||||
$check_columns = $pdo->query("SHOW COLUMNS FROM documents LIKE 'id_conductor'");
|
||||
if ($check_columns->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE documents ADD COLUMN id_conductor INT, ADD FOREIGN KEY (id_conductor) REFERENCES taxis(id) ON DELETE SET NULL;");
|
||||
}
|
||||
$check_columns = $pdo->query("SHOW COLUMNS FROM documents LIKE 'tipo_documento'");
|
||||
if ($check_columns->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE documents ADD COLUMN tipo_documento VARCHAR(255);");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("DB ERROR: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// --- File Upload & Update Logic ---
|
||||
$upload_dir = 'uploads/';
|
||||
$message = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pdo = db();
|
||||
// Handle Update
|
||||
if (isset($_POST['update_document'])) {
|
||||
$id = $_POST['document_id'];
|
||||
$title = $_POST['title'];
|
||||
$description = $_POST['description'];
|
||||
$id_conductor = $_POST['id_conductor'];
|
||||
$tipo_documento = $_POST['tipo_documento'];
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE documents SET title = ?, description = ?, id_conductor = ?, tipo_documento = ? WHERE id = ?");
|
||||
$stmt->execute([$title, $description, $id_conductor, $tipo_documento, $id]);
|
||||
$message = '<div class="alert alert-success">Documento actualizado con éxito.</div>';
|
||||
} catch (PDOException $e) {
|
||||
$message = '<div class="alert alert-danger">Error al actualizar: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
// Handle Upload
|
||||
elseif (isset($_FILES['document'])) {
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0755, true);
|
||||
}
|
||||
|
||||
$title = $_POST['title'] ?? 'Sin Título';
|
||||
$description = $_POST['description'] ?? '';
|
||||
$id_conductor = $_POST['id_conductor'] ?? null;
|
||||
$tipo_documento = $_POST['tipo_documento'] ?? '';
|
||||
$file_name = basename($_FILES['document']['name']);
|
||||
$file_tmp = $_FILES['document']['tmp_name'];
|
||||
$file_error = $_FILES['document']['error'];
|
||||
|
||||
if ($file_error === UPLOAD_ERR_OK) {
|
||||
$safe_file_name = uniqid() . '-' . preg_replace("/[^a-zA-Z0-9._-]/", "", $file_name);
|
||||
$file_path = $upload_dir . $safe_file_name;
|
||||
|
||||
if (move_uploaded_file($file_tmp, $file_path)) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO documents (title, description, id_conductor, tipo_documento, file_name, file_path) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$title, $description, $id_conductor, $tipo_documento, $file_name, $file_path]);
|
||||
$message = '<div class="alert alert-success">Documento subido con éxito.</div>';
|
||||
} catch (PDOException $e) {
|
||||
$message = '<div class="alert alert-danger">Error al guardar en la base de datos: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error al mover el fichero subido.</div>';
|
||||
}
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger">Error en la subida del fichero: ' . $file_error . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fetch Data ---
|
||||
$documents = [];
|
||||
$conductores = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Fetch drivers (taxis)
|
||||
$conductores = $pdo->query("SELECT id, matricula, modelo FROM taxis ORDER BY matricula")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch documents with driver info
|
||||
$sort_column = $_GET['sort'] ?? 'uploaded_at';
|
||||
$sort_order = $_GET['order'] ?? 'DESC';
|
||||
$valid_columns = ['title', 'tipo_documento', 'conductor', 'uploaded_at'];
|
||||
if (!in_array($sort_column, $valid_columns)) {
|
||||
$sort_column = 'uploaded_at';
|
||||
}
|
||||
|
||||
$sql = "SELECT d.id, d.title, d.description, d.file_name, d.file_path, d.uploaded_at, d.tipo_documento, t.matricula as conductor, d.id_conductor
|
||||
FROM documents d
|
||||
LEFT JOIN taxis t ON d.id_conductor = t.id
|
||||
ORDER BY $sort_column $sort_order";
|
||||
|
||||
$stmt = $pdo->query($sql);
|
||||
$documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message .= '<div class="alert alert-danger">Error al obtener los datos: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
|
||||
function get_sort_link($column, $display) {
|
||||
$sort_column = $_GET['sort'] ?? 'uploaded_at';
|
||||
$sort_order = $_GET['order'] ?? 'DESC';
|
||||
$order = ($sort_column == $column && $sort_order == 'ASC') ? 'DESC' : 'ASC';
|
||||
$icon = $sort_column == $column ? ($sort_order == 'ASC' ? 'bi-sort-up' : 'bi-sort-down') : 'bi-arrow-down-up-square';
|
||||
return '<a href="?sort=' . $column . '&order=' . $order . '">' . $display . ' <i class="bi '.$icon.'"></i></a>';
|
||||
}
|
||||
|
||||
include 'header.php';
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2">Gestor de Documentos</h1>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
|
||||
<i class="bi bi-upload"></i> Subir Documento
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php echo $message; ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th><?php echo get_sort_link('title', 'Título'); ?></th>
|
||||
<th>Descripción</th>
|
||||
<th><?php echo get_sort_link('tipo_documento', 'Tipo'); ?></th>
|
||||
<th><?php echo get_sort_link('conductor', 'Conductor'); ?></th>
|
||||
<th>Fichero</th>
|
||||
<th><?php echo get_sort_link('uploaded_at', 'Fecha de Subida'); ?></th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($documents)): ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">No hay documentos todavía.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($documents as $doc): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($doc['title']); ?></td>
|
||||
<td><?php echo htmlspecialchars($doc['description']); ?></td>
|
||||
<td><?php echo htmlspecialchars($doc['tipo_documento']); ?></td>
|
||||
<td><?php echo htmlspecialchars($doc['conductor'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo htmlspecialchars($doc['file_name']); ?></td>
|
||||
<td><?php echo date("d/m/Y H:i", strtotime($doc['uploaded_at'])); ?></td>
|
||||
<td>
|
||||
<a href="<?php echo htmlspecialchars($doc['file_path']); ?>" class="btn btn-sm btn-outline-primary" download>
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary edit-btn"
|
||||
data-id="<?php echo $doc['id']; ?>"
|
||||
data-title="<?php echo htmlspecialchars($doc['title']); ?>"
|
||||
data-description="<?php echo htmlspecialchars($doc['description']); ?>"
|
||||
data-id-conductor="<?php echo $doc['id_conductor']; ?>"
|
||||
data-tipo-documento="<?php echo htmlspecialchars($doc['tipo_documento']); ?>"
|
||||
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Modal -->
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="uploadModalLabel">Subir Nuevo Documento</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="documents.php" method="post" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Título</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Descripción</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="id_conductor_upload" class="form-label">Conductor</label>
|
||||
<select class="form-select" id="id_conductor_upload" name="id_conductor">
|
||||
<option value="">Sin asignar</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>"><?php echo htmlspecialchars($conductor['matricula']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="tipo_documento" class="form-label">Tipo de Documento</label>
|
||||
<input type="text" class="form-control" id="tipo_documento" name="tipo_documento" placeholder="Ej: ITV, Seguro, DNI...">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="document" class="form-label">Seleccionar Fichero</label>
|
||||
<input class="form-control" type="file" id="document" name="document" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="submit" class="btn btn-primary">Subir</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Editar Documento</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="documents.php" method="post">
|
||||
<input type="hidden" name="update_document" value="1">
|
||||
<input type="hidden" id="edit_document_id" name="document_id">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="edit_title" class="form-label">Título</label>
|
||||
<input type="text" class="form-control" id="edit_title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_description" class="form-label">Descripción</label>
|
||||
<textarea class="form-control" id="edit_description" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_id_conductor" class="form-label">Conductor</label>
|
||||
<select class="form-select" id="edit_id_conductor" name="id_conductor">
|
||||
<option value="">Sin asignar</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>"><?php echo htmlspecialchars($conductor['matricula']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_tipo_documento" class="form-label">Tipo de Documento</label>
|
||||
<input type="text" class="form-control" id="edit_tipo_documento" name="tipo_documento">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const modal = this;
|
||||
|
||||
modal.querySelector('#edit_document_id').value = button.dataset.id;
|
||||
modal.querySelector('#edit_title').value = button.dataset.title;
|
||||
modal.querySelector('#edit_description').value = button.dataset.description;
|
||||
modal.querySelector('#edit_id_conductor').value = button.dataset.idConductor;
|
||||
modal.querySelector('#edit_tipo_documento').value = button.dataset.tipoDocumento;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
256
drivers.php
256
drivers.php
@ -1,256 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
$message = '';
|
||||
$message_type = 'success';
|
||||
|
||||
// --- DB Schema Migration ---
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Add new columns to taxis table if they don't exist
|
||||
$columns = [
|
||||
'nombre' => 'VARCHAR(255) NULL',
|
||||
'licencia' => 'VARCHAR(100) NULL',
|
||||
'municipio' => "ENUM('TIAS', 'TEGUISE', 'YAIZA') NULL",
|
||||
'ultima_localizacion_lat' => 'DECIMAL(10, 8) NULL',
|
||||
'ultima_localizacion_lng' => 'DECIMAL(11, 8) NULL',
|
||||
'id_departamento' => 'INT NULL'
|
||||
];
|
||||
|
||||
foreach ($columns as $column => $type) {
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM taxis LIKE '$column'");
|
||||
if ($stmt->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE taxis ADD COLUMN `$column` $type;");
|
||||
}
|
||||
}
|
||||
|
||||
// Add foreign key for id_departamento separately to avoid issues in loop
|
||||
$stmt = $pdo->query("SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema = DATABASE() AND table_name = 'taxis' AND constraint_name = 'taxis_ibfk_1'");
|
||||
if($stmt->rowCount() == 0) {
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM taxis LIKE 'id_departamento'");
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$pdo->exec("ALTER TABLE taxis ADD FOREIGN KEY (id_departamento) REFERENCES departamentos(id) ON DELETE SET NULL;");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['add_conductor'])) {
|
||||
$sql = "INSERT INTO taxis (nombre, licencia, matricula, municipio, id_departamento) VALUES (?, ?, ?, ?, ?)";
|
||||
$pdo->prepare($sql)->execute([$_POST['nombre'], $_POST['licencia'], $_POST['matricula'], $_POST['municipio'], $_POST['id_departamento'] ?: null]);
|
||||
$message = 'Conductor añadido con éxito.';
|
||||
} elseif (isset($_POST['edit_conductor'])) {
|
||||
$sql = "UPDATE taxis SET nombre=?, licencia=?, matricula=?, municipio=?, id_departamento=? WHERE id=?";
|
||||
$pdo->prepare($sql)->execute([$_POST['nombre'], $_POST['licencia'], $_POST['matricula'], $_POST['municipio'], $_POST['id_departamento'] ?: null, $_POST['id']]);
|
||||
$message = 'Conductor actualizado con éxito.';
|
||||
} elseif (isset($_POST['delete_conductor'])) {
|
||||
$sql = "DELETE FROM taxis WHERE id=?";
|
||||
$pdo->prepare($sql)->execute([$_POST['id']]);
|
||||
$message = 'Conductor eliminado con éxito.';
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data for table
|
||||
$conductores = $pdo->query("SELECT
|
||||
t.id, t.nombre, t.licencia, t.matricula, t.municipio, t.ultima_localizacion_lat, t.ultima_localizacion_lng,
|
||||
(SELECT COUNT(*) FROM documents WHERE id_conductor = t.id) as num_documentos,
|
||||
(SELECT COUNT(*) FROM citas WHERE id_conductor = t.id) as num_citas,
|
||||
(SELECT COUNT(*) FROM consultas WHERE id_conductor = t.id) as num_consultas,
|
||||
(SELECT COUNT(*) FROM localizacion_historico WHERE id_taxi = t.id) as num_ubicaciones
|
||||
FROM taxis t
|
||||
GROUP BY t.id
|
||||
ORDER BY t.nombre ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$departamentos = $pdo->query("SELECT id, nombre FROM departamentos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Error de base de datos: ' . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gestión de Conductores</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Conductores</li>
|
||||
</ol>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-<?php echo $message_type; ?> alert-dismissible fade show" role="alert">
|
||||
<?php echo htmlspecialchars($message); ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-users me-1"></i>
|
||||
Listado de Conductores
|
||||
<button type="button" class="btn btn-primary btn-sm float-end" data-bs-toggle="modal" data-bs-target="#formConductorModal" onclick="prepareAddForm()">
|
||||
<i class="fas fa-plus"></i> Añadir Conductor
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover" id="conductoresTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Licencia</th>
|
||||
<th>Matrícula</th>
|
||||
<th>Municipio</th>
|
||||
<th>Ubicación</th>
|
||||
<th>Docs</th>
|
||||
<th>Citas</th>
|
||||
<th>Consultas</th>
|
||||
<th>Ubics.</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<tr>
|
||||
<td><?php echo $conductor['id']; ?></td>
|
||||
<td><?php echo htmlspecialchars($conductor['nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($conductor['licencia']); ?></td>
|
||||
<td><?php echo htmlspecialchars($conductor['matricula']); ?></td>
|
||||
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($conductor['municipio']); ?></span></td>
|
||||
<td>
|
||||
<?php if($conductor['ultima_localizacion_lat']): ?>
|
||||
<small><?php echo $conductor['ultima_localizacion_lat'] . ', ' . $conductor['ultima_localizacion_lng']; ?></small>
|
||||
<?php else: echo 'N/A'; endif; ?>
|
||||
</td>
|
||||
<td><span class="badge bg-info"><?php echo $conductor['num_documentos']; ?></span></td>
|
||||
<td><span class="badge bg-info"><?php echo $conductor['num_citas']; ?></span></td>
|
||||
<td><span class="badge bg-info"><?php echo $conductor['num_consultas']; ?></span></td>
|
||||
<td><span class="badge bg-info"><?php echo $conductor['num_ubicaciones']; ?></span></td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick='prepareEditForm(<?php echo json_encode($conductor); ?>)'><i class="fas fa-edit"></i></button>
|
||||
<a href="documents.php?id_conductor=<?php echo $conductor['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Añadir Documento"><i class="fas fa-file-medical"></i></a>
|
||||
<a href="citas.php?id_conductor=<?php echo $conductor['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Añadir Cita"><i class="fas fa-calendar-plus"></i></a>
|
||||
<a href="consultas.php?id_conductor=<?php echo $conductor['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Añadir Consulta"><i class="fas fa-question-circle"></i></a>
|
||||
<a href="localizacion.php?id_taxi=<?php echo $conductor['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Añadir Ubicación"><i class="fas fa-map-marker-alt"></i></a>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick='prepareDeleteForm(<?php echo $conductor['id']; ?>)'><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Conductor Modal -->
|
||||
<div class="modal fade" id="formConductorModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="conductorForm" action="drivers.php" method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="formModalLabel"></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="id" id="conductor_id">
|
||||
<div id="formMethod"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre</label>
|
||||
<input type="text" class="form-control" name="nombre" id="conductor_nombre" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Licencia</label>
|
||||
<input type="text" class="form-control" name="licencia" id="conductor_licencia">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Matrícula</label>
|
||||
<input type="text" class="form-control" name="matricula" id="conductor_matricula" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Municipio</label>
|
||||
<select class="form-select" name="municipio" id="conductor_municipio">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="TIAS">Tías</option>
|
||||
<option value="TEGUISE">Teguise</option>
|
||||
<option value="YAIZA">Yaiza</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Departamento</label>
|
||||
<select class="form-select" name="id_departamento" id="conductor_departamento">
|
||||
<option value="">Sin asignar</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>"><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteConductorModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="drivers.php" method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Confirmar Eliminación</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>¿Estás seguro de que quieres eliminar a este conductor? Esta acción no se puede deshacer.</p>
|
||||
<input type="hidden" name="delete_conductor" value="1">
|
||||
<input type="hidden" name="id" id="delete_conductor_id">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-danger">Eliminar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function prepareAddForm() {
|
||||
document.getElementById('conductorForm').reset();
|
||||
document.getElementById('formModalLabel').innerText = 'Añadir Conductor';
|
||||
document.getElementById('formMethod').innerHTML = '<input type="hidden" name="add_conductor" value="1">';
|
||||
new bootstrap.Modal(document.getElementById('formConductorModal')).show();
|
||||
}
|
||||
|
||||
function prepareEditForm(conductor) {
|
||||
document.getElementById('conductorForm').reset();
|
||||
document.getElementById('formModalLabel').innerText = 'Editar Conductor';
|
||||
document.getElementById('formMethod').innerHTML = '<input type="hidden" name="edit_conductor" value="1">';
|
||||
|
||||
document.getElementById('conductor_id').value = conductor.id;
|
||||
document.getElementById('conductor_nombre').value = conductor.nombre;
|
||||
document.getElementById('conductor_licencia').value = conductor.licencia;
|
||||
document.getElementById('conductor_matricula').value = conductor.matricula;
|
||||
document.getElementById('conductor_municipio').value = conductor.municipio;
|
||||
document.getElementById('conductor_departamento').value = conductor.id_departamento;
|
||||
|
||||
new bootstrap.Modal(document.getElementById('formConductorModal')).show();
|
||||
}
|
||||
|
||||
function prepareDeleteForm(id) {
|
||||
document.getElementById('delete_conductor_id').value = id;
|
||||
new bootstrap.Modal(document.getElementById('deleteConductorModal')).show();
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
12
footer.php
12
footer.php
@ -1,12 +0,0 @@
|
||||
</main>
|
||||
|
||||
<footer class="bg-white text-center text-lg-start mt-5 py-3">
|
||||
<div class="text-center text-muted">
|
||||
© <?php echo date("Y"); ?> <?php echo htmlspecialchars(getenv('PROJECT_NAME') ?: 'Taxi HRM'); ?>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
66
header.php
66
header.php
@ -1,66 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo htmlspecialchars(getenv('PROJECT_NAME') ?: 'Taxi HRM'); ?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars(getenv('PROJECT_DESCRIPTION') ?: 'HRM for Taxi Business'); ?>">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.navbar {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.1);
|
||||
}
|
||||
.card {
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,.05);
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #0D6EFD;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">
|
||||
<i class="bi bi-taxi-front-fill"></i>
|
||||
<?php echo htmlspecialchars(getenv('PROJECT_NAME') ?: 'Taxi HRM'); ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.php"><i class="bi bi-speedometer2"></i> Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="drivers.php"><i class="bi bi-person-badge"></i> Conductores</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="departamentos.php"><i class="bi bi-building"></i> Departamentos</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="localizacion.php"><i class="bi bi-geo-alt"></i> Mapa</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
175
horarios.php
175
horarios.php
@ -1,175 +0,0 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
$message = '';
|
||||
$message_type = '';
|
||||
|
||||
// --- DB Schema and Data Fetching ---
|
||||
try {
|
||||
$pdo = db();
|
||||
// Add department FK to appointment_slots table
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS appointment_slots (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_departamento INT NOT NULL,
|
||||
day_of_week TINYINT NOT NULL, -- 1 for Monday, 7 for Sunday
|
||||
start_time TIME NOT NULL,
|
||||
end_time TIME NOT NULL,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
UNIQUE KEY (id_departamento, day_of_week, start_time, end_time),
|
||||
FOREIGN KEY (id_departamento) REFERENCES departamentos(id) ON DELETE CASCADE
|
||||
);");
|
||||
|
||||
// Fetch departments
|
||||
$departamentos = $pdo->query("SELECT id, nombre FROM departamentos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Get selected department (from GET param or first available)
|
||||
$selected_depto_id = $_GET['id_departamento'] ?? ($departamentos[0]['id'] ?? null);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Error de conexión con la base de datos: ' . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
|
||||
// Handle POST request to update slots
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($pdo) && isset($_POST['id_departamento'])) {
|
||||
$id_departamento_post = $_POST['id_departamento'];
|
||||
try {
|
||||
// Deactivate all existing slots for the specific department first
|
||||
$stmt_deactivate = $pdo->prepare("UPDATE appointment_slots SET is_active = false WHERE id_departamento = ?");
|
||||
$stmt_deactivate->execute([$id_departamento_post]);
|
||||
|
||||
if (isset($_POST['slots'])) {
|
||||
$slots = $_POST['slots'];
|
||||
$stmt_upsert = $pdo->prepare(
|
||||
"INSERT INTO appointment_slots (id_departamento, day_of_week, start_time, end_time, is_active)
|
||||
VALUES (:id_departamento, :day_of_week, :start_time, :end_time, true)
|
||||
ON DUPLICATE KEY UPDATE is_active = true"
|
||||
);
|
||||
|
||||
foreach ($slots as $day => $times) {
|
||||
foreach ($times as $time_range => $on) {
|
||||
list($start_time, $end_time) = explode('-', $time_range);
|
||||
$stmt_upsert->execute([
|
||||
':id_departamento' => $id_departamento_post,
|
||||
':day_of_week' => $day,
|
||||
':start_time' => $start_time,
|
||||
':end_time' => $end_time
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$message = 'Horarios actualizados correctamente para el departamento seleccionado.';
|
||||
$message_type = 'success';
|
||||
$selected_depto_id = $id_departamento_post; // Keep the current department selected
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Error al actualizar los horarios: ' . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch current active slots for the selected department
|
||||
$active_slots = [];
|
||||
if (isset($pdo) && $selected_depto_id) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT day_of_week, start_time, end_time FROM appointment_slots WHERE is_active = true AND id_departamento = ? ORDER BY day_of_week, start_time");
|
||||
$stmt->execute([$selected_depto_id]);
|
||||
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$active_slots[$row['day_of_week']][date('H:i', strtotime($row['start_time'])) . '-' . date('H:i', strtotime($row['end_time']))] = true;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$message = 'Error al obtener los horarios: ' . $e->getMessage();
|
||||
$message_type = 'danger';
|
||||
}
|
||||
}
|
||||
|
||||
$days_of_week = [1 => 'Lunes', 2 => 'Martes', 3 => 'Miércoles', 4 => 'Jueves', 5 => 'Viernes', 6 => 'Sábado', 7 => 'Domingo'];
|
||||
$time_ranges = [];
|
||||
for ($h = 8; $h < 20; $h++) {
|
||||
$start = str_pad($h, 2, '0', STR_PAD_LEFT) . ':00';
|
||||
$end = str_pad($h + 1, 2, '0', STR_PAD_LEFT) . ':00';
|
||||
$time_ranges[] = $start . '-' . $end;
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.schedule-grid { display: grid; grid-template-columns: 120px repeat(<?php echo count($time_ranges); ?>, 1fr); gap: 5px; overflow-x: auto; }
|
||||
.grid-header, .day-label, .slot-checkbox { padding: 10px; text-align: center; background-color: #f8f9fa; font-size: 0.8rem; white-space: nowrap; }
|
||||
.day-label { font-weight: bold; position: sticky; left: 0; z-index: 1; }
|
||||
.slot-checkbox { background-color: #fff; }
|
||||
</style>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Gestión de Horarios para Citas</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Horarios</li>
|
||||
</ol>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-<?php echo $message_type; ?> alert-dismissible fade show" role="alert">
|
||||
<?php echo htmlspecialchars($message); ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-calendar-alt me-1"></i>
|
||||
Seleccionar Departamento
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="horarios.php" method="GET" id="depto-select-form">
|
||||
<div class="input-group">
|
||||
<select class="form-select" name="id_departamento" onchange="document.getElementById('depto-select-form').submit();">
|
||||
<option value="">Seleccione un departamento</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>" <?php echo ($depto['id'] == $selected_depto_id) ? 'selected' : ''; ?>><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($selected_depto_id): ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Marque los bloques de tiempo disponibles</h5>
|
||||
<p class="card-text">Seleccione las franjas horarias disponibles para el departamento seleccionado. Los cambios guardados se aplicarán solo a este departamento.</p>
|
||||
<form action="horarios.php?id_departamento=<?php echo $selected_depto_id; ?>" method="POST">
|
||||
<input type="hidden" name="id_departamento" value="<?php echo $selected_depto_id; ?>">
|
||||
<div class="schedule-grid mb-3">
|
||||
<div class="grid-header day-label">Día</div>
|
||||
<?php foreach ($time_ranges as $range): ?>
|
||||
<div class="grid-header"><?php echo str_replace('-', ' - ', $range); ?></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php foreach ($days_of_week as $day_num => $day_name): ?>
|
||||
<div class="day-label"><?php echo $day_name; ?></div>
|
||||
<?php foreach ($time_ranges as $range): ?>
|
||||
<div class="slot-checkbox">
|
||||
<input class="form--input" type="checkbox"
|
||||
name="slots[<?php echo $day_num; ?>][<?php echo $range; ?>]"
|
||||
<?php echo isset($active_slots[$day_num][$range]) ? 'checked' : ''; ?>>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary">Guardar Horarios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info">Por favor, seleccione un departamento para ver y gestionar sus horarios.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
189
index.php
189
index.php
@ -1,189 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
// Fetch data for selectors
|
||||
$conductores = [];
|
||||
$departamentos = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$conductores = $pdo->query("SELECT id, matricula, nombre FROM taxis ORDER BY nombre ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$departamentos = $pdo->query("SELECT id, nombre FROM departamentos ORDER BY nombre ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
// Silently fail, selectors will be empty
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.dashboard-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
.role-card {
|
||||
background-color: #fff;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
padding: 2rem;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
.role-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.12);
|
||||
}
|
||||
.role-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.role-card-header i {
|
||||
font-size: 1.8rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.role-card-header h3 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: #343a40;
|
||||
}
|
||||
.role-card .form-select {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.action-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.action-list li a {
|
||||
display: block;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
.action-list li a:hover {
|
||||
background-color: #e9ecef;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.action-list li a i {
|
||||
margin-right: 0.75rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.hidden-list {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Dashboard Interactivo</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item active">Seleccione un rol para empezar</li>
|
||||
</ol>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<!-- Administrador Card -->
|
||||
<div class="role-card" id="admin-card">
|
||||
<div class="role-card-header">
|
||||
<i class="bi bi-shield-lock-fill"></i>
|
||||
<h3>Administrador</h3>
|
||||
</div>
|
||||
<ul class="action-list">
|
||||
<li><a href="drivers.php"><i class="bi bi-people-fill"></i>Conductores y Taxis</a></li>
|
||||
<li><a href="departamentos.php"><i class="bi bi-building"></i>Departamentos</a></li>
|
||||
<li><a href="documents.php"><i class="bi bi-file-earmark-text"></i>Documentos</a></li>
|
||||
<li><a href="citas.php"><i class="bi bi-calendar-check"></i>Citas</a></li>
|
||||
<li><a href="consultas.php"><i class="bi bi-question-circle"></i>Consultas</a></li>
|
||||
<li><a href="horarios.php"><i class="bi bi-clock"></i>Horarios de Citas</a></li>
|
||||
<li><a href="localizacion.php"><i class="bi bi-pin-map-fill"></i>Localizaciones de Taxis</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Conductor Card -->
|
||||
<div class="role-card" id="conductor-card">
|
||||
<div class="role-card-header">
|
||||
<i class="bi bi-person-badge-fill"></i>
|
||||
<h3>Conductor</h3>
|
||||
</div>
|
||||
<select class="form-select" id="conductor-selector">
|
||||
<option value="">Seleccione un conductor...</option>
|
||||
<?php foreach ($conductores as $conductor): ?>
|
||||
<option value="<?php echo $conductor['id']; ?>"><?php echo htmlspecialchars($conductor['nombre'] . ' (' . $conductor['matricula'] . ')'); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<ul class="action-list hidden-list" id="conductor-actions">
|
||||
<li><a href="#" data-url="drivers.php?action=edit&id="><i class="bi bi-person-lines-fill"></i>Mi Información</a></li>
|
||||
<li><a href="#" data-url="documents.php?id_conductor="><i class="bi bi-file-earmark-text"></i>Mis Documentos</a></li>
|
||||
<li><a href="#" data-url="citas.php?id_conductor="><i class="bi bi-calendar-check"></i>Mis Citas</a></li>
|
||||
<li><a href="#" data-url="consultas.php?id_conductor="><i class="bi bi-question-circle"></i>Mis Consultas</a></li>
|
||||
<li><a href="#" data-url="localizacion.php?action=show_history&id_taxi="><i class="bi bi-clock-history"></i>Mi Historial de Ubicaciones</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Departamento Card -->
|
||||
<div class="role-card" id="departamento-card">
|
||||
<div class="role-card-header">
|
||||
<i class="bi bi-diagram-3-fill"></i>
|
||||
<h3>Departamento</h3>
|
||||
</div>
|
||||
<select class="form-select" id="departamento-selector">
|
||||
<option value="">Seleccione un departamento...</option>
|
||||
<?php foreach ($departamentos as $depto): ?>
|
||||
<option value="<?php echo $depto['id']; ?>"><?php echo htmlspecialchars($depto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<ul class="action-list hidden-list" id="departamento-actions">
|
||||
<li><a href="#" data-url="departamentos.php?action=edit&id="><i class="bi bi-pencil-square"></i>Ver/Editar Departamento</a></li>
|
||||
<li><a href="#" data-url="citas.php?id_departamento="><i class="bi bi-calendar-check"></i>Citas del Departamento</a></li>
|
||||
<li><a href="#" data-url="consultas.php?id_departamento="><i class="bi bi-question-circle"></i>Consultas del Departamento</a></li>
|
||||
<li><a href="#" data-url="horarios.php?id_departamento="><i class="bi bi-clock"></i>Horarios del Departamento</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const conductorSelector = document.getElementById('conductor-selector');
|
||||
const conductorActions = document.getElementById('conductor-actions');
|
||||
const departamentoSelector = document.getElementById('departamento-selector');
|
||||
const departamentoActions = document.getElementById('departamento-actions');
|
||||
|
||||
conductorSelector.addEventListener('change', function() {
|
||||
const conductorId = this.value;
|
||||
if (conductorId) {
|
||||
conductorActions.classList.remove('hidden-list');
|
||||
conductorActions.querySelectorAll('a').forEach(link => {
|
||||
const baseUrl = link.getAttribute('data-url');
|
||||
link.href = baseUrl + conductorId;
|
||||
});
|
||||
} else {
|
||||
conductorActions.classList.add('hidden-list');
|
||||
}
|
||||
});
|
||||
|
||||
departamentoSelector.addEventListener('change', function() {
|
||||
const deptoId = this.value;
|
||||
if (deptoId) {
|
||||
departamentoActions.classList.remove('hidden-list');
|
||||
departamentoActions.querySelectorAll('a').forEach(link => {
|
||||
const baseUrl = link.getAttribute('data-url');
|
||||
link.href = baseUrl + deptoId;
|
||||
});
|
||||
} else {
|
||||
departamentoActions.classList.add('hidden-list');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
237
localizacion.php
237
localizacion.php
@ -1,237 +0,0 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
// The schema creation is kept for robustness, but handled in drivers.php mainly.
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS localizacion_taxis (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_taxi INT NOT NULL UNIQUE,
|
||||
latitud DECIMAL(10, 8) NOT NULL,
|
||||
longitud DECIMAL(11, 8) NOT NULL,
|
||||
ultima_actualizacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_taxi) REFERENCES taxis(id) ON DELETE CASCADE
|
||||
)");
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS localizacion_historico (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_taxi INT NOT NULL,
|
||||
latitud DECIMAL(10, 8) NOT NULL,
|
||||
longitud DECIMAL(11, 8) NOT NULL,
|
||||
fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_taxi) REFERENCES taxis(id) ON DELETE CASCADE
|
||||
)");
|
||||
|
||||
// Handle historical location submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_localizacion_historica'])) {
|
||||
$id_taxi = $_POST['id_taxi'];
|
||||
$latitud = $_POST['latitud'];
|
||||
$longitud = $_POST['longitud'];
|
||||
$fecha_registro = $_POST['fecha_registro'];
|
||||
|
||||
if (!empty($id_taxi) && is_numeric($latitud) && is_numeric($longitud) && !empty($fecha_registro)) {
|
||||
$stmt = $pdo->prepare("INSERT INTO localizacion_historico (id_taxi, latitud, longitud, fecha_registro) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$id_taxi, $latitud, $longitud, $fecha_registro]);
|
||||
|
||||
// Also update the taxi's main location for immediate reflection
|
||||
$stmt_update = $pdo->prepare("UPDATE taxis SET ultima_localizacion_lat = ?, ultima_localizacion_lng = ? WHERE id = ?");
|
||||
$stmt_update->execute([$latitud, $longitud, $id_taxi]);
|
||||
|
||||
echo '<div class="alert alert-success">Ubicación histórica añadida y ubicación principal del conductor actualizada.</div>';
|
||||
} else {
|
||||
echo '<div class="alert alert-danger">Todos los campos son obligatorios.</div>';
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
die("Error de base de datos: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
<style>
|
||||
.map-container {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 1rem;
|
||||
height: 60vh; /* Adjust height as needed */
|
||||
}
|
||||
#map-sidebar {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#map-sidebar .list-group-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
#map {
|
||||
border-radius: 0.5rem;
|
||||
height: 100%;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.leaflet-popup-content b { color: #0d6efd; }
|
||||
</style>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Mapa General de Taxis</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Mapa</li>
|
||||
</ol>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="map-container">
|
||||
<div id="map-sidebar">
|
||||
<div class="d-grid mb-2">
|
||||
<button class="btn btn-secondary" id="show-all-btn">Mostrar Todos</button>
|
||||
</div>
|
||||
<ul class="list-group" id="taxi-list"></ul>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="fas fa-plus me-1"></i>Añadir Ubicación Histórica</div>
|
||||
<div class="card-body">
|
||||
<form action="localizacion.php" method="POST">
|
||||
<p class="small text-muted">Haz clic en un taxi del mapa para rellenar los datos o haz clic en el mapa para obtener coordenadas.</p>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputTaxiInfo" type="text" placeholder="Taxi" readonly />
|
||||
<label>Taxi Seleccionado</label>
|
||||
<input type="hidden" id="inputTaxiId" name="id_taxi" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputLatitud" type="text" name="latitud" placeholder="Latitud" required />
|
||||
<label>Latitud</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputLongitud" type="text" name="longitud" placeholder="Longitud" required />
|
||||
<label>Longitud</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputFecha" type="datetime-local" name="fecha_registro" required />
|
||||
<label>Fecha y Hora</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid"><button type="submit" name="add_localizacion_historica" class="btn btn-primary">Añadir a Historial</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const map = L.map('map').setView([28.963, -13.548], 11); // Centered on Lanzarote
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);
|
||||
|
||||
const markers = {};
|
||||
const taxiList = document.getElementById('taxi-list');
|
||||
|
||||
const icons = {
|
||||
'TIAS': new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }),
|
||||
'TEGUISE': new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-orange.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }),
|
||||
'YAIZA': new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }),
|
||||
'default': new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] })
|
||||
};
|
||||
|
||||
function createPopupContent(taxi) {
|
||||
return `
|
||||
<b>${taxi.nombre || 'Conductor sin nombre'}</b><br>
|
||||
<b>Matrícula:</b> ${taxi.matricula}<br>
|
||||
<b>Licencia:</b> ${taxi.licencia || 'N/A'}<br>
|
||||
<b>Municipio:</b> ${taxi.municipio || 'N/A'}<br><hr>
|
||||
<b>Docs:</b> ${taxi.num_documentos} |
|
||||
<b>Citas:</b> ${taxi.num_citas} |
|
||||
<b>Consultas:</b> ${taxi.num_consultas} |
|
||||
<b>Ubics.:</b> ${taxi.num_ubicaciones}
|
||||
`;
|
||||
}
|
||||
|
||||
function updateMap() {
|
||||
fetch('api.php?action=get_current_locations')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
taxiList.innerHTML = ''; // Clear list before update
|
||||
const activeTaxiIds = data.map(t => t.id);
|
||||
|
||||
// Remove old markers
|
||||
Object.keys(markers).forEach(id => {
|
||||
if (!activeTaxiIds.includes(parseInt(id))) {
|
||||
map.removeLayer(markers[id]);
|
||||
delete markers[id];
|
||||
}
|
||||
});
|
||||
|
||||
data.forEach(taxi => {
|
||||
const lat = parseFloat(taxi.latitud);
|
||||
const lon = parseFloat(taxi.longitud);
|
||||
if (isNaN(lat) || isNaN(lon)) return;
|
||||
|
||||
// Update sidebar
|
||||
const listItem = document.createElement('li');
|
||||
listItem.className = 'list-group-item list-group-item-action';
|
||||
listItem.textContent = `${taxi.matricula} (${taxi.nombre || 'Sin nombre'})`;
|
||||
listItem.dataset.taxiId = taxi.id;
|
||||
listItem.addEventListener('click', () => {
|
||||
map.setView([lat, lon], 15);
|
||||
markers[taxi.id].openPopup();
|
||||
});
|
||||
taxiList.appendChild(listItem);
|
||||
|
||||
// Update map markers
|
||||
const popupContent = createPopupContent(taxi);
|
||||
const icon = icons[taxi.municipio] || icons['default'];
|
||||
if (markers[taxi.id]) {
|
||||
markers[taxi.id].setLatLng([lat, lon]).setPopupContent(popupContent).setIcon(icon);
|
||||
} else {
|
||||
markers[taxi.id] = L.marker([lat, lon], { icon: icon }).addTo(map)
|
||||
.bindPopup(popupContent);
|
||||
}
|
||||
|
||||
markers[taxi.id].off('click').on('click', () => {
|
||||
document.getElementById('inputTaxiInfo').value = `${taxi.matricula} - ${taxi.nombre}`;
|
||||
document.getElementById('inputTaxiId').value = taxi.id;
|
||||
document.getElementById('inputLatitud').value = lat.toFixed(8);
|
||||
document.getElementById('inputLongitud').value = lon.toFixed(8);
|
||||
document.getElementById('inputFecha').value = new Date().toISOString().slice(0, 16);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('show-all-btn').addEventListener('click', () => {
|
||||
const group = new L.featureGroup(Object.values(markers));
|
||||
if (Object.keys(markers).length > 0) {
|
||||
map.fitBounds(group.getBounds().pad(0.2));
|
||||
}
|
||||
});
|
||||
|
||||
map.on('click', function(e) {
|
||||
document.getElementById('inputLatitud').value = e.latlng.lat.toFixed(8);
|
||||
document.getElementById('inputLongitud').value = e.latlng.lng.toFixed(8);
|
||||
});
|
||||
|
||||
setInterval(updateMap, 10000); // Update every 10 seconds
|
||||
updateMap(); // Initial load
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once 'footer.php';
|
||||
?>
|
||||
@ -1,235 +0,0 @@
|
||||
<?php
|
||||
// Minimal mail service for the workspace app (VM).
|
||||
// Usage:
|
||||
// require_once __DIR__ . '/MailService.php';
|
||||
// // Generic:
|
||||
// MailService::sendMail($to, $subject, $htmlBody, $textBody = null, $opts = []);
|
||||
// // Contact form helper:
|
||||
// MailService::sendContactMessage($name, $email, $message, $to = null, $subject = 'New contact form');
|
||||
|
||||
class MailService
|
||||
{
|
||||
// Universal mail sender (no attachments by design)
|
||||
public static function sendMail($to, string $subject, string $htmlBody, ?string $textBody = null, array $opts = [])
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer not available' ];
|
||||
}
|
||||
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost');
|
||||
$fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App');
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
if (!empty($opts['reply_to']) && filter_var($opts['reply_to'], FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($opts['reply_to']);
|
||||
} elseif (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Recipients
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) $toList = array_map('trim', explode(',', $to));
|
||||
elseif (is_array($to)) $toList = $to;
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) { $mail->addAddress($addr); $added++; }
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
foreach ((array)($opts['cc'] ?? []) as $cc) { if (filter_var($cc, FILTER_VALIDATE_EMAIL)) $mail->addCC($cc); }
|
||||
foreach ((array)($opts['bcc'] ?? []) as $bcc){ if (filter_var($bcc, FILTER_VALIDATE_EMAIL)) $mail->addBCC($bcc); }
|
||||
|
||||
// Optional DKIM
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody ?? strip_tags($htmlBody);
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
private static function loadConfig(): array
|
||||
{
|
||||
$configPath = __DIR__ . '/config.php';
|
||||
if (!file_exists($configPath)) {
|
||||
throw new \RuntimeException('Mail config not found. Copy mail/config.sample.php to mail/config.php and fill in credentials.');
|
||||
}
|
||||
$cfg = require $configPath;
|
||||
if (!is_array($cfg)) {
|
||||
throw new \RuntimeException('Invalid mail config format: expected array');
|
||||
}
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
// Send a contact message
|
||||
// $to can be: a single email string, a comma-separated list, an array of emails, or null (fallback to MAIL_TO/MAIL_FROM)
|
||||
public static function sendContactMessage(string $name, string $email, string $message, $to = null, string $subject = 'New contact form')
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
|
||||
// Try Composer autoload if available (for PHPMailer)
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
// Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
// Debian/Ubuntu package layout (libphp-phpmailer)
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
// Alternative layout (older PHPMailer package names)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
$transport = $cfg['transport'] ?? 'smtp';
|
||||
if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
// Fallback: attempt native mail() — works only if MTA is configured on the VM
|
||||
return self::sendViaNativeMail($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['smtp_host'] ?? '';
|
||||
$mail->Port = (int)($cfg['smtp_port'] ?? 587);
|
||||
$secure = $cfg['smtp_secure'] ?? 'tls';
|
||||
if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
|
||||
elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
else $mail->SMTPSecure = false;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['smtp_user'] ?? '';
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $cfg['from_email'] ?? 'no-reply@localhost';
|
||||
$fromName = $cfg['from_name'] ?? 'App';
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
|
||||
// Use Reply-To for the user's email to avoid spoofing From
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($email, $name ?: $email);
|
||||
}
|
||||
if (!empty($cfg['reply_to'])) {
|
||||
$mail->addReplyTo($cfg['reply_to']);
|
||||
}
|
||||
|
||||
// Destination: prefer dynamic recipients ($to), fallback to MAIL_TO; no silent FROM fallback
|
||||
$toList = [];
|
||||
if ($to) {
|
||||
if (is_string($to)) {
|
||||
// allow comma-separated list
|
||||
$toList = array_map('trim', explode(',', $to));
|
||||
} elseif (is_array($to)) {
|
||||
$toList = $to;
|
||||
}
|
||||
} elseif (!empty(getenv('MAIL_TO'))) {
|
||||
$toList = array_map('trim', explode(',', getenv('MAIL_TO')));
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($toList as $addr) {
|
||||
if (filter_var($addr, FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addAddress($addr);
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
if ($added === 0) {
|
||||
return [ 'success' => false, 'error' => 'No recipients defined (set MAIL_TO or pass $to)' ];
|
||||
}
|
||||
|
||||
// DKIM (optional)
|
||||
if (!empty($cfg['dkim_domain']) && !empty($cfg['dkim_selector']) && !empty($cfg['dkim_private_key_path'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim_domain'];
|
||||
$mail->DKIM_selector = $cfg['dkim_selector'];
|
||||
$mail->DKIM_private = $cfg['dkim_private_key_path'];
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
|
||||
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}";
|
||||
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
|
||||
private static function sendViaNativeMail(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$opts = ['reply_to' => $email];
|
||||
$html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
return self::sendMail($to, $subject, $html, $body, $opts);
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
// Mail configuration sourced from environment variables.
|
||||
// No secrets are stored here; the file just maps env -> config array for MailService.
|
||||
|
||||
function env_val(string $key, $default = null) {
|
||||
$v = getenv($key);
|
||||
return ($v === false || $v === null || $v === '') ? $default : $v;
|
||||
}
|
||||
|
||||
// Fallback: if critical vars are missing from process env, try to parse executor/.env
|
||||
// This helps in web/Apache contexts where .env is not exported.
|
||||
// Supports simple KEY=VALUE lines; ignores quotes and comments.
|
||||
function load_dotenv_if_needed(array $keys): void {
|
||||
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
|
||||
if (empty($missing)) return;
|
||||
static $loaded = false;
|
||||
if ($loaded) return;
|
||||
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||
if ($envPath && is_readable($envPath)) {
|
||||
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
if ($line[0] === '#' || trim($line) === '') continue;
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
// Strip potential surrounding quotes
|
||||
$v = trim($v, "\"' ");
|
||||
// Do not override existing env
|
||||
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
|
||||
putenv("{$k}={$v}");
|
||||
}
|
||||
}
|
||||
$loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
load_dotenv_if_needed([
|
||||
'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS',
|
||||
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO',
|
||||
'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH'
|
||||
]);
|
||||
|
||||
$transport = env_val('MAIL_TRANSPORT', 'smtp');
|
||||
$smtp_host = env_val('SMTP_HOST');
|
||||
$smtp_port = (int) env_val('SMTP_PORT', 587);
|
||||
$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
|
||||
$smtp_user = env_val('SMTP_USER');
|
||||
$smtp_pass = env_val('SMTP_PASS');
|
||||
|
||||
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
|
||||
$from_name = env_val('MAIL_FROM_NAME', 'App');
|
||||
$reply_to = env_val('MAIL_REPLY_TO');
|
||||
|
||||
$dkim_domain = env_val('DKIM_DOMAIN');
|
||||
$dkim_selector = env_val('DKIM_SELECTOR');
|
||||
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
||||
|
||||
return [
|
||||
'transport' => $transport,
|
||||
|
||||
// SMTP
|
||||
'smtp_host' => $smtp_host,
|
||||
'smtp_port' => $smtp_port,
|
||||
'smtp_secure' => $smtp_secure,
|
||||
'smtp_user' => $smtp_user,
|
||||
'smtp_pass' => $smtp_pass,
|
||||
|
||||
// From / Reply-To
|
||||
'from_email' => $from_email,
|
||||
'from_name' => $from_name,
|
||||
'reply_to' => $reply_to,
|
||||
|
||||
// DKIM (optional)
|
||||
'dkim_domain' => $dkim_domain,
|
||||
'dkim_selector' => $dkim_selector,
|
||||
'dkim_private_key_path' => $dkim_private_key_path,
|
||||
];
|
||||
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^7.0.7"
|
||||
}
|
||||
}
|
||||
35
phpunit.xml
Normal file
35
phpunit.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
</include>
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
<env name="NIGHTWATCH_ENABLED" value="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
25
public/.htaccess
Normal file
25
public/.htaccess
Normal file
@ -0,0 +1,25 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
0
public/favicon.ico
Normal file
0
public/favicon.ico
Normal file
20
public/index.php
Normal file
20
public/index.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->handleRequest(Request::capture());
|
||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
11
resources/css/app.css
Normal file
11
resources/css/app.css
Normal file
@ -0,0 +1,11 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
1
resources/js/app.js
Normal file
1
resources/js/app.js
Normal file
@ -0,0 +1 @@
|
||||
import './bootstrap';
|
||||
4
resources/js/bootstrap.js
vendored
Normal file
4
resources/js/bootstrap.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
1
resources/views/citas/create.blade.php
Normal file
1
resources/views/citas/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear Cita</h1>
|
||||
1
resources/views/citas/edit.blade.php
Normal file
1
resources/views/citas/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar Cita</h1>
|
||||
1
resources/views/citas/index.blade.php
Normal file
1
resources/views/citas/index.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Listado de Citas</h1>
|
||||
1
resources/views/citas/show.blade.php
Normal file
1
resources/views/citas/show.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Ver Cita</h1>
|
||||
1
resources/views/consultas/create.blade.php
Normal file
1
resources/views/consultas/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear Consulta</h1>
|
||||
1
resources/views/consultas/edit.blade.php
Normal file
1
resources/views/consultas/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar Consulta</h1>
|
||||
1
resources/views/consultas/index.blade.php
Normal file
1
resources/views/consultas/index.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Listado de Consultas</h1>
|
||||
1
resources/views/consultas/show.blade.php
Normal file
1
resources/views/consultas/show.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Ver Consulta</h1>
|
||||
1
resources/views/departamentos/create.blade.php
Normal file
1
resources/views/departamentos/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear Departamento</h1>
|
||||
1
resources/views/departamentos/edit.blade.php
Normal file
1
resources/views/departamentos/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar Departamento</h1>
|
||||
1
resources/views/departamentos/index.blade.php
Normal file
1
resources/views/departamentos/index.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Listado de Departamentos</h1>
|
||||
1
resources/views/departamentos/show.blade.php
Normal file
1
resources/views/departamentos/show.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Ver Departamento</h1>
|
||||
1
resources/views/documentos/create.blade.php
Normal file
1
resources/views/documentos/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear Documento</h1>
|
||||
1
resources/views/documentos/edit.blade.php
Normal file
1
resources/views/documentos/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar Documento</h1>
|
||||
1
resources/views/documentos/index.blade.php
Normal file
1
resources/views/documentos/index.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Listado de Documentos</h1>
|
||||
1
resources/views/documentos/show.blade.php
Normal file
1
resources/views/documentos/show.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Ver Documento</h1>
|
||||
1
resources/views/localizacion_historico/create.blade.php
Normal file
1
resources/views/localizacion_historico/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear LocalizacionHistorico</h1>
|
||||
1
resources/views/localizacion_historico/edit.blade.php
Normal file
1
resources/views/localizacion_historico/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar LocalizacionHistorico</h1>
|
||||
1
resources/views/localizacion_historico/index.blade.php
Normal file
1
resources/views/localizacion_historico/index.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Listado de LocalizacionHistorico</h1>
|
||||
1
resources/views/localizacion_historico/show.blade.php
Normal file
1
resources/views/localizacion_historico/show.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Ver LocalizacionHistorico</h1>
|
||||
1
resources/views/localizacion_taxi/create.blade.php
Normal file
1
resources/views/localizacion_taxi/create.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Crear LocalizacionTaxi</h1>
|
||||
1
resources/views/localizacion_taxi/edit.blade.php
Normal file
1
resources/views/localizacion_taxi/edit.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h1>Editar LocalizacionTaxi</h1>
|
||||
53
resources/views/localizacion_taxi/index.blade.php
Normal file
53
resources/views/localizacion_taxi/index.blade.php
Normal file
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mapa de Taxis</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
<style>
|
||||
#map { height: 100vh; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var map = L.map('map').setView([40.416775, -3.703790], 12); // Centrado en Madrid por defecto
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
|
||||
function fetchTaxiLocations() {
|
||||
fetch('/api/taxis/locations')
|
||||
.then(response => response.json())
|
||||
.then(locations => {
|
||||
// Limpiar marcadores existentes
|
||||
map.eachLayer(function (layer) {
|
||||
if (layer instanceof L.Marker) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Añadir nuevos marcadores
|
||||
locations.forEach(location => {
|
||||
L.marker([location.latitud, location.longitud])
|
||||
.addTo(map)
|
||||
.bindPopup(`<b>Taxi ID:</b> ${location.taxi_id}<br><b>Última actualización:</b> ${new Date(location.updated_at).toLocaleString()}`);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error al cargar las localizaciones:', error));
|
||||
}
|
||||
|
||||
// Cargar localizaciones al inicio y luego cada 10 segundos
|
||||
fetchTaxiLocations();
|
||||
setInterval(fetchTaxiLocations, 10000);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user