Initial import of animal_management module
This commit is contained in:
182
Events.php
Normal file
182
Events.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\services\GalleryIntegrationService;
|
||||
use humhub\modules\file\models\File;
|
||||
use humhub\modules\post\models\Post;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class Events
|
||||
{
|
||||
public static function onSpaceMenuInit($event): void
|
||||
{
|
||||
$space = $event->sender->space ?? null;
|
||||
if ($space === null || !$space->moduleManager->isEnabled('animal_management')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->sender->addItem([
|
||||
'label' => Yii::t('AnimalManagementModule.base', 'Animals'),
|
||||
'group' => 'modules',
|
||||
'url' => $space->createUrl('/animal_management/animals/index'),
|
||||
'icon' => '<i class="fa fa-paw"></i>',
|
||||
'sortOrder' => 110,
|
||||
'isActive' => (
|
||||
Yii::$app->controller
|
||||
&& Yii::$app->controller->module
|
||||
&& Yii::$app->controller->module->id === 'animal_management'
|
||||
&& Yii::$app->controller->id === 'animals'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function onRescueSettingsMenuInit($event): void
|
||||
{
|
||||
$space = $event->sender->space ?? null;
|
||||
if ($space === null || !$space->moduleManager->isEnabled('animal_management')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->sender->addItem([
|
||||
'label' => Yii::t('AnimalManagementModule.base', 'Animal Management'),
|
||||
'url' => $space->createUrl('/animal_management/settings'),
|
||||
'sortOrder' => 300,
|
||||
'isActive' => (
|
||||
Yii::$app->controller
|
||||
&& Yii::$app->controller->module
|
||||
&& Yii::$app->controller->module->id === 'animal_management'
|
||||
&& Yii::$app->controller->id === 'settings'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function onSpaceAdminMenuInitFallback($event): void
|
||||
{
|
||||
$space = $event->sender->space ?? null;
|
||||
if ($space === null || !$space->moduleManager->isEnabled('animal_management')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($space->moduleManager->isEnabled('rescue_foundation') || !$space->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->sender->addItem([
|
||||
'label' => Yii::t('AnimalManagementModule.base', 'Animal Management'),
|
||||
'group' => 'admin',
|
||||
'url' => $space->createUrl('/animal_management/settings'),
|
||||
'icon' => '<i class="fa fa-paw"></i>',
|
||||
'sortOrder' => 600,
|
||||
'isActive' => (
|
||||
Yii::$app->controller
|
||||
&& Yii::$app->controller->module
|
||||
&& Yii::$app->controller->module->id === 'animal_management'
|
||||
&& Yii::$app->controller->id === 'settings'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function onPostAfterSave($event): void
|
||||
{
|
||||
$post = $event->sender ?? null;
|
||||
if (!$post instanceof Post) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_item', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = trim((string)$post->message);
|
||||
if ($message === '' || stripos($message, '#gallery') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $post->content;
|
||||
$container = $content->container ?? null;
|
||||
if (!$container instanceof Space) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$container->moduleManager->isEnabled('animal_management')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachedFiles = File::findByRecord($post);
|
||||
if (empty($attachedFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$imageFiles = [];
|
||||
foreach ($attachedFiles as $file) {
|
||||
if (!$file instanceof File) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos((string)$file->mime_type, 'image/') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imageFiles[] = $file;
|
||||
}
|
||||
|
||||
if (empty($imageFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$animals = Animal::find()
|
||||
->where(['contentcontainer_id' => $container->contentcontainer_id])
|
||||
->all();
|
||||
|
||||
if (empty($animals)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$matchedAnimals = [];
|
||||
$searchableMessage = strtolower(strip_tags($message));
|
||||
foreach ($animals as $animal) {
|
||||
$uid = strtolower(trim((string)$animal->animal_uid));
|
||||
if ($uid === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($searchableMessage, $uid) !== false) {
|
||||
$matchedAnimals[] = $animal;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($matchedAnimals)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$createdBy = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
foreach ($matchedAnimals as $animal) {
|
||||
foreach ($imageFiles as $file) {
|
||||
$exists = AnimalGalleryItem::findOne([
|
||||
'animal_id' => (int)$animal->id,
|
||||
'file_id' => (int)$file->id,
|
||||
]);
|
||||
|
||||
if ($exists instanceof AnimalGalleryItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = new AnimalGalleryItem();
|
||||
$item->animal_id = (int)$animal->id;
|
||||
$item->file_id = (int)$file->id;
|
||||
$item->source_post_id = (int)$post->id;
|
||||
$item->source_type = 'post';
|
||||
$item->created_by = $createdBy;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
if ($container instanceof Space) {
|
||||
GalleryIntegrationService::ensureAnimalGallery($animal, $container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Module.php
Normal file
37
Module.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management;
|
||||
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use humhub\modules\content\components\ContentContainerModule;
|
||||
use humhub\modules\space\models\Space;
|
||||
|
||||
class Module extends ContentContainerModule
|
||||
{
|
||||
public $resourcesPath = 'resources';
|
||||
|
||||
public function getContentContainerTypes()
|
||||
{
|
||||
return [Space::class];
|
||||
}
|
||||
|
||||
public function getPermissions($contentContainer = null)
|
||||
{
|
||||
if ($contentContainer instanceof Space) {
|
||||
return [
|
||||
new permissions\ManageAnimals(),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getContentContainerConfigUrl(ContentContainerActiveRecord $container)
|
||||
{
|
||||
if ($container->moduleManager->isEnabled('rescue_foundation')) {
|
||||
return $container->createUrl('/rescue_foundation/settings');
|
||||
}
|
||||
|
||||
return $container->createUrl('/animal_management/settings');
|
||||
}
|
||||
}
|
||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Animal Management Module
|
||||
|
||||
Animal intake, profile management, medical/progress tracking, gallery integration, and transfer workflows for rescue spaces in HumHub.
|
||||
|
||||
## Requirements
|
||||
|
||||
- HumHub 1.14+
|
||||
- `rescue_foundation` module available in the same HumHub instance
|
||||
|
||||
## Installation
|
||||
|
||||
Clone into the HumHub modules path using the folder name `animal_management`:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.kelinreij.duckdns.org/humhub-modules/animal-management.git /var/www/localhost/htdocs/protected/modules/animal_management
|
||||
```
|
||||
|
||||
Enable the module in HumHub (Marketplace / Modules UI), then run migrations:
|
||||
|
||||
```bash
|
||||
php /var/www/localhost/htdocs/protected/yii migrate/up --include-module-migrations=1 --interactive=0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Transfer notifications rely on HumHub queue processing.
|
||||
- If you run in Docker, execute commands inside the HumHub app container.
|
||||
51
components/UrlRule.php
Normal file
51
components/UrlRule.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\components;
|
||||
|
||||
use humhub\components\ContentContainerUrlRuleInterface;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use yii\base\Component;
|
||||
use yii\web\UrlManager;
|
||||
use yii\web\UrlRuleInterface;
|
||||
|
||||
class UrlRule extends Component implements UrlRuleInterface, ContentContainerUrlRuleInterface
|
||||
{
|
||||
public const CONTAINER_ROUTE = 'animal_management/animals/index';
|
||||
private const ROUTE_SEGMENT = 'animals';
|
||||
|
||||
public function parseContentContainerRequest(ContentContainerActiveRecord $container, UrlManager $manager, string $containerUrlPath, array $urlParams)
|
||||
{
|
||||
if ($containerUrlPath === self::ROUTE_SEGMENT) {
|
||||
return [self::CONTAINER_ROUTE, $urlParams];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createContentContainerUrl(UrlManager $manager, string $containerUrlPath, string $route, array $params)
|
||||
{
|
||||
if (ltrim($route, '/') !== self::CONTAINER_ROUTE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = $containerUrlPath . '/' . self::ROUTE_SEGMENT;
|
||||
if (!empty($params)) {
|
||||
$query = http_build_query($params);
|
||||
if ($query !== '') {
|
||||
$url .= '?' . $query;
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function createUrl($manager, $route, $params)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function parseRequest($manager, $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
config.php
Normal file
22
config.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\Events;
|
||||
use humhub\modules\post\models\Post;
|
||||
use humhub\modules\space\widgets\HeaderControlsMenu;
|
||||
use humhub\modules\space\widgets\Menu;
|
||||
|
||||
return [
|
||||
'id' => 'animal_management',
|
||||
'class' => 'humhub\\modules\\animal_management\\Module',
|
||||
'namespace' => 'humhub\\modules\\animal_management',
|
||||
'urlManagerRules' => [
|
||||
['class' => 'humhub\\modules\\animal_management\\components\\UrlRule'],
|
||||
],
|
||||
'events' => [
|
||||
[Menu::class, Menu::EVENT_INIT, [Events::class, 'onSpaceMenuInit']],
|
||||
['humhub\\modules\\rescue_foundation\\widgets\\RescueSettingsMenu', 'rescueSettingsMenuInit', [Events::class, 'onRescueSettingsMenuInit']],
|
||||
[HeaderControlsMenu::class, HeaderControlsMenu::EVENT_INIT, [Events::class, 'onSpaceAdminMenuInitFallback']],
|
||||
[Post::class, Post::EVENT_AFTER_INSERT, [Events::class, 'onPostAfterSave']],
|
||||
[Post::class, Post::EVENT_AFTER_UPDATE, [Events::class, 'onPostAfterSave']],
|
||||
],
|
||||
];
|
||||
1710
controllers/AnimalsController.php
Normal file
1710
controllers/AnimalsController.php
Normal file
File diff suppressed because it is too large
Load Diff
58
controllers/SettingsController.php
Normal file
58
controllers/SettingsController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\controllers;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
|
||||
use humhub\modules\animal_management\models\forms\FieldDefinitionSettingsForm;
|
||||
use humhub\modules\content\components\ContentContainerController;
|
||||
use humhub\modules\content\components\ContentContainerControllerAccess;
|
||||
use humhub\modules\rescue_foundation\widgets\RescueSettingsMenu;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class SettingsController extends ContentContainerController
|
||||
{
|
||||
protected function getAccessRules()
|
||||
{
|
||||
return [[ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [Space::USERGROUP_OWNER, Space::USERGROUP_ADMIN]]];
|
||||
}
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
$subNav = null;
|
||||
if (class_exists(RescueSettingsMenu::class)) {
|
||||
$subNav = RescueSettingsMenu::widget(['space' => $this->contentContainer]);
|
||||
}
|
||||
|
||||
$fieldSettingsForm = new FieldDefinitionSettingsForm();
|
||||
$fieldSettingsForm->loadRows();
|
||||
$displaySettingsForm = new DisplaySettingsForm([
|
||||
'contentContainer' => $this->contentContainer,
|
||||
]);
|
||||
$displaySettingsForm->loadValues();
|
||||
|
||||
if (Yii::$app->request->post('DisplaySettingsForm') !== null) {
|
||||
if ($displaySettingsForm->load(Yii::$app->request->post()) && $displaySettingsForm->save()) {
|
||||
$this->view->success(Yii::t('AnimalManagementModule.base', 'Display settings saved.'));
|
||||
return $this->redirect($this->contentContainer->createUrl('/animal_management/settings'));
|
||||
}
|
||||
}
|
||||
|
||||
if (Yii::$app->request->post('FieldDefinitionSettingsForm') !== null) {
|
||||
if ($fieldSettingsForm->load(Yii::$app->request->post()) && $fieldSettingsForm->save()) {
|
||||
$this->view->success(Yii::t('AnimalManagementModule.base', 'Field settings saved.'));
|
||||
return $this->redirect($this->contentContainer->createUrl('/animal_management/settings'));
|
||||
}
|
||||
}
|
||||
|
||||
$animalCount = Animal::find()->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id])->count();
|
||||
|
||||
return $this->render('index', [
|
||||
'subNav' => $subNav,
|
||||
'animalCount' => (int)$animalCount,
|
||||
'fieldSettingsForm' => $fieldSettingsForm,
|
||||
'displaySettingsForm' => $displaySettingsForm,
|
||||
]);
|
||||
}
|
||||
}
|
||||
25
helpers/DateDisplayHelper.php
Normal file
25
helpers/DateDisplayHelper.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\helpers;
|
||||
|
||||
use Yii;
|
||||
|
||||
class DateDisplayHelper
|
||||
{
|
||||
public const DATETIME_FORMAT = 'php:m/d/Y h:i A';
|
||||
|
||||
public static function format(?string $value): string
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return Yii::$app->formatter->asDatetime($timestamp, self::DATETIME_FORMAT);
|
||||
}
|
||||
}
|
||||
160
migrations/m260401_210000_initial.php
Normal file
160
migrations/m260401_210000_initial.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260401_210000_initial extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeCreateTable('rescue_animal', [
|
||||
'id' => $this->primaryKey(),
|
||||
'contentcontainer_id' => $this->integer()->notNull(),
|
||||
'animal_uid' => $this->string(190)->notNull(),
|
||||
'name' => $this->string(190)->null(),
|
||||
'species' => $this->string(120)->null(),
|
||||
'breed' => $this->string(120)->null(),
|
||||
'sex' => $this->string(32)->null(),
|
||||
'status' => $this->string(32)->notNull()->defaultValue('intake'),
|
||||
'in_possession' => $this->boolean()->notNull()->defaultValue(1),
|
||||
'location_name' => $this->string(120)->null(),
|
||||
'city' => $this->string(120)->null(),
|
||||
'state' => $this->string(2)->null(),
|
||||
'zip' => $this->string(10)->null(),
|
||||
'previous_owner_user_id' => $this->integer()->null(),
|
||||
'public_summary' => $this->text()->null(),
|
||||
'medical_notes' => $this->text()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('ux_rescue_animal_uid', 'rescue_animal', 'animal_uid', true);
|
||||
$this->safeCreateIndex('idx_rescue_animal_container', 'rescue_animal', 'contentcontainer_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_status', 'rescue_animal', 'status', false);
|
||||
$this->safeAddForeignKey('fk_rescue_animal_container', 'rescue_animal', 'contentcontainer_id', 'contentcontainer', 'id', 'CASCADE', 'CASCADE');
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('user', true) !== null) {
|
||||
$this->safeAddForeignKey('fk_rescue_animal_prev_owner', 'rescue_animal', 'previous_owner_user_id', 'user', 'id', 'SET NULL', 'CASCADE');
|
||||
}
|
||||
|
||||
$this->safeCreateTable('rescue_animal_transfer', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'from_contentcontainer_id' => $this->integer()->notNull(),
|
||||
'to_contentcontainer_id' => $this->integer()->notNull(),
|
||||
'requested_by' => $this->integer()->null(),
|
||||
'status' => $this->string(32)->notNull()->defaultValue('requested'),
|
||||
'request_message' => $this->text()->null(),
|
||||
'conditions_text' => $this->text()->null(),
|
||||
'responded_at' => $this->dateTime()->null(),
|
||||
'completed_at' => $this->dateTime()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_animal_transfer_animal', 'rescue_animal_transfer', 'animal_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_transfer_status', 'rescue_animal_transfer', 'status', false);
|
||||
$this->safeAddForeignKey('fk_rescue_animal_transfer_animal', 'rescue_animal_transfer', 'animal_id', 'rescue_animal', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->safeAddForeignKey('fk_rescue_animal_transfer_from_container', 'rescue_animal_transfer', 'from_contentcontainer_id', 'contentcontainer', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->safeAddForeignKey('fk_rescue_animal_transfer_to_container', 'rescue_animal_transfer', 'to_contentcontainer_id', 'contentcontainer', 'id', 'CASCADE', 'CASCADE');
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('user', true) !== null) {
|
||||
$this->safeAddForeignKey('fk_rescue_animal_transfer_requested_by', 'rescue_animal_transfer', 'requested_by', 'user', 'id', 'SET NULL', 'CASCADE');
|
||||
}
|
||||
|
||||
$this->safeCreateTable('rescue_animal_medical_visit', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'visit_at' => $this->dateTime()->null(),
|
||||
'provider_name' => $this->string(190)->null(),
|
||||
'notes' => $this->text()->null(),
|
||||
'recommendations' => $this->text()->null(),
|
||||
'created_by' => $this->integer()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_medical_visit_animal', 'rescue_animal_medical_visit', 'animal_id', false);
|
||||
$this->safeAddForeignKey('fk_rescue_medical_visit_animal', 'rescue_animal_medical_visit', 'animal_id', 'rescue_animal', 'id', 'CASCADE', 'CASCADE');
|
||||
|
||||
$this->safeCreateTable('rescue_animal_progress_update', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'update_at' => $this->dateTime()->null(),
|
||||
'weight' => $this->string(32)->null(),
|
||||
'vitals' => $this->text()->null(),
|
||||
'behavior_notes' => $this->text()->null(),
|
||||
'meal_plan_changes' => $this->text()->null(),
|
||||
'housing_changes' => $this->text()->null(),
|
||||
'medical_concerns' => $this->text()->null(),
|
||||
'post_to_space_feed' => $this->boolean()->notNull()->defaultValue(0),
|
||||
'post_to_animal_feed' => $this->boolean()->notNull()->defaultValue(1),
|
||||
'created_by' => $this->integer()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_progress_animal', 'rescue_animal_progress_update', 'animal_id', false);
|
||||
$this->safeAddForeignKey('fk_rescue_progress_animal', 'rescue_animal_progress_update', 'animal_id', 'rescue_animal', 'id', 'CASCADE', 'CASCADE');
|
||||
|
||||
$this->seedFieldMetadata();
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_progress_update');
|
||||
$this->safeDropTable('rescue_animal_medical_visit');
|
||||
$this->safeDropTable('rescue_animal_transfer');
|
||||
$this->safeDropTable('rescue_animal');
|
||||
}
|
||||
|
||||
private function seedFieldMetadata(): void
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$createdAt = date('Y-m-d H:i:s');
|
||||
$rows = [
|
||||
['name', 'Animal name', 'text', 0, 1, 100],
|
||||
['species', 'Species', 'text', 0, 1, 110],
|
||||
['breed', 'Breed', 'text', 0, 1, 120],
|
||||
['sex', 'Sex', 'text', 0, 1, 130],
|
||||
['status', 'Status', 'select', 1, 1, 140],
|
||||
['in_possession', 'In possession', 'boolean', 1, 1, 150],
|
||||
['location_name', 'Location name', 'text', 0, 1, 160],
|
||||
['city', 'City', 'text', 0, 1, 170],
|
||||
['state', 'State', 'text', 0, 1, 180],
|
||||
['zip', 'ZIP', 'text', 0, 1, 190],
|
||||
['public_summary', 'Public summary', 'textarea', 0, 1, 200],
|
||||
['medical_notes', 'Medical notes', 'textarea', 0, 1, 210],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
[$fieldKey, $label, $inputType, $required, $isCore, $sortOrder] = $row;
|
||||
$exists = (new \yii\db\Query())
|
||||
->from('rescue_field_definition')
|
||||
->where(['module_id' => 'animal_management', 'field_key' => $fieldKey])
|
||||
->exists($this->db);
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insert('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $label,
|
||||
'input_type' => $inputType,
|
||||
'required' => $required,
|
||||
'is_core' => $isCore,
|
||||
'is_active' => 1,
|
||||
'visibility' => 'public',
|
||||
'options' => '{}',
|
||||
'sort_order' => $sortOrder,
|
||||
'created_at' => $createdAt,
|
||||
'updated_at' => $createdAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
migrations/m260402_010000_transfer_event_audit.php
Normal file
63
migrations/m260402_010000_transfer_event_audit.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_010000_transfer_event_audit extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeCreateTable('rescue_animal_transfer_event', [
|
||||
'id' => $this->primaryKey(),
|
||||
'transfer_id' => $this->integer()->notNull(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'event_type' => $this->string(32)->notNull(),
|
||||
'from_status' => $this->string(32)->null(),
|
||||
'to_status' => $this->string(32)->null(),
|
||||
'message' => $this->text()->null(),
|
||||
'metadata_json' => $this->text()->null(),
|
||||
'created_by' => $this->integer()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_transfer_event_transfer', 'rescue_animal_transfer_event', 'transfer_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_transfer_event_animal', 'rescue_animal_transfer_event', 'animal_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_transfer_event_type', 'rescue_animal_transfer_event', 'event_type', false);
|
||||
$this->safeCreateIndex('idx_rescue_transfer_event_created_at', 'rescue_animal_transfer_event', 'created_at', false);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_transfer_event_transfer',
|
||||
'rescue_animal_transfer_event',
|
||||
'transfer_id',
|
||||
'rescue_animal_transfer',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_transfer_event_animal',
|
||||
'rescue_animal_transfer_event',
|
||||
'animal_id',
|
||||
'rescue_animal',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('user', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_transfer_event_created_by',
|
||||
'rescue_animal_transfer_event',
|
||||
'created_by',
|
||||
'user',
|
||||
'id',
|
||||
'SET NULL',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_transfer_event');
|
||||
}
|
||||
}
|
||||
50
migrations/m260402_020000_animal_field_values.php
Normal file
50
migrations/m260402_020000_animal_field_values.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_020000_animal_field_values extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeCreateTable('rescue_animal_field_value', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'field_definition_id' => $this->integer()->notNull(),
|
||||
'value_text' => $this->text()->null(),
|
||||
'value_json' => $this->text()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('ux_rescue_animal_field_value_unique', 'rescue_animal_field_value', ['animal_id', 'field_definition_id'], true);
|
||||
$this->safeCreateIndex('idx_rescue_animal_field_value_animal', 'rescue_animal_field_value', 'animal_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_field_value_definition', 'rescue_animal_field_value', 'field_definition_id', false);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_field_value_animal',
|
||||
'rescue_animal_field_value',
|
||||
'animal_id',
|
||||
'rescue_animal',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_field_value_definition',
|
||||
'rescue_animal_field_value',
|
||||
'field_definition_id',
|
||||
'rescue_field_definition',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_field_value');
|
||||
}
|
||||
}
|
||||
117
migrations/m260402_030000_medical_progress_field_values.php
Normal file
117
migrations/m260402_030000_medical_progress_field_values.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_030000_medical_progress_field_values extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeCreateTable('rescue_animal_medical_visit_field_value', [
|
||||
'id' => $this->primaryKey(),
|
||||
'medical_visit_id' => $this->integer()->notNull(),
|
||||
'field_definition_id' => $this->integer()->notNull(),
|
||||
'value_text' => $this->text()->null(),
|
||||
'value_json' => $this->text()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex(
|
||||
'ux_rescue_medical_visit_field_value_unique',
|
||||
'rescue_animal_medical_visit_field_value',
|
||||
['medical_visit_id', 'field_definition_id'],
|
||||
true
|
||||
);
|
||||
$this->safeCreateIndex(
|
||||
'idx_rescue_medical_visit_field_value_visit',
|
||||
'rescue_animal_medical_visit_field_value',
|
||||
'medical_visit_id',
|
||||
false
|
||||
);
|
||||
$this->safeCreateIndex(
|
||||
'idx_rescue_medical_visit_field_value_definition',
|
||||
'rescue_animal_medical_visit_field_value',
|
||||
'field_definition_id',
|
||||
false
|
||||
);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_medical_visit_field_value_visit',
|
||||
'rescue_animal_medical_visit_field_value',
|
||||
'medical_visit_id',
|
||||
'rescue_animal_medical_visit',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_medical_visit_field_value_definition',
|
||||
'rescue_animal_medical_visit_field_value',
|
||||
'field_definition_id',
|
||||
'rescue_field_definition',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
$this->safeCreateTable('rescue_animal_progress_update_field_value', [
|
||||
'id' => $this->primaryKey(),
|
||||
'progress_update_id' => $this->integer()->notNull(),
|
||||
'field_definition_id' => $this->integer()->notNull(),
|
||||
'value_text' => $this->text()->null(),
|
||||
'value_json' => $this->text()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex(
|
||||
'ux_rescue_progress_update_field_value_unique',
|
||||
'rescue_animal_progress_update_field_value',
|
||||
['progress_update_id', 'field_definition_id'],
|
||||
true
|
||||
);
|
||||
$this->safeCreateIndex(
|
||||
'idx_rescue_progress_update_field_value_update',
|
||||
'rescue_animal_progress_update_field_value',
|
||||
'progress_update_id',
|
||||
false
|
||||
);
|
||||
$this->safeCreateIndex(
|
||||
'idx_rescue_progress_update_field_value_definition',
|
||||
'rescue_animal_progress_update_field_value',
|
||||
'field_definition_id',
|
||||
false
|
||||
);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_progress_update_field_value_update',
|
||||
'rescue_animal_progress_update_field_value',
|
||||
'progress_update_id',
|
||||
'rescue_animal_progress_update',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_progress_update_field_value_definition',
|
||||
'rescue_animal_progress_update_field_value',
|
||||
'field_definition_id',
|
||||
'rescue_field_definition',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_progress_update_field_value');
|
||||
$this->safeDropTable('rescue_animal_medical_visit_field_value');
|
||||
}
|
||||
}
|
||||
61
migrations/m260402_040000_add_image_profile_fields.php
Normal file
61
migrations/m260402_040000_add_image_profile_fields.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_040000_add_image_profile_fields extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$createdAt = date('Y-m-d H:i:s');
|
||||
$rows = [
|
||||
['cover_image_url', 'Cover image URL', 230],
|
||||
['profile_image_url', 'Profile image URL', 240],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
[$fieldKey, $label, $sortOrder] = $row;
|
||||
|
||||
$exists = (new \yii\db\Query())
|
||||
->from('rescue_field_definition')
|
||||
->where(['module_id' => 'animal_management', 'field_key' => $fieldKey])
|
||||
->exists($this->db);
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insert('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $label,
|
||||
'input_type' => 'text',
|
||||
'required' => 0,
|
||||
'is_core' => 0,
|
||||
'is_active' => 1,
|
||||
'visibility' => 'public',
|
||||
'options' => '{}',
|
||||
'sort_order' => $sortOrder,
|
||||
'created_at' => $createdAt,
|
||||
'updated_at' => $createdAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->delete('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'field_key' => ['cover_image_url', 'profile_image_url'],
|
||||
'is_core' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
160
migrations/m260402_050000_seed_requirement_default_fields.php
Normal file
160
migrations/m260402_050000_seed_requirement_default_fields.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_050000_seed_requirement_default_fields extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$createdAt = date('Y-m-d H:i:s');
|
||||
|
||||
$rows = [
|
||||
// Animal profile defaults from requirements
|
||||
['animal_profile', 'dob', 'DOB', 'date', 0, 1, 'public', 112],
|
||||
['animal_profile', 'age', 'Age', 'text', 0, 1, 'public', 113],
|
||||
['animal_profile', 'previous_owner_user_id', 'Previous Owner User ID', 'number', 0, 1, 'internal', 114],
|
||||
['animal_profile', 'previous_owner_name', 'Previous Owner Name', 'text', 0, 1, 'internal', 115],
|
||||
['animal_profile', 'previous_owner_business_name', 'Previous Owner Business Name', 'text', 0, 1, 'internal', 116],
|
||||
['animal_profile', 'previous_owner_street_address', 'Previous Owner Street Address', 'text', 0, 1, 'internal', 117],
|
||||
['animal_profile', 'previous_owner_city', 'Previous Owner City', 'text', 0, 1, 'internal', 118],
|
||||
['animal_profile', 'previous_owner_state', 'Previous Owner State', 'text', 0, 1, 'internal', 119],
|
||||
['animal_profile', 'previous_owner_zip', 'Previous Owner Zip', 'text', 0, 1, 'internal', 120],
|
||||
['animal_profile', 'previous_owner_cell_phone', 'Previous Owner Cell Phone', 'text', 0, 1, 'internal', 121],
|
||||
['animal_profile', 'previous_owner_business_phone', 'Previous Owner Business Phone', 'text', 0, 1, 'internal', 122],
|
||||
['animal_profile', 'previous_owner_email', 'Previous Owner Email', 'text', 0, 1, 'internal', 123],
|
||||
['animal_profile', 'lineage', 'Lineage', 'textarea', 0, 1, 'public', 124],
|
||||
['animal_profile', 'backstory', 'Backstory', 'textarea', 0, 1, 'public', 125],
|
||||
['animal_profile', 'rescue', 'Rescue', 'text', 0, 1, 'internal', 126],
|
||||
|
||||
// Medical visit defaults from requirements
|
||||
['animal_medical_visit', 'weight', 'Weight', 'text', 0, 1, 'internal', 210],
|
||||
['animal_medical_visit', 'pulse', 'Pulse', 'text', 0, 1, 'internal', 211],
|
||||
['animal_medical_visit', 'blood_pressure', 'Blood Pressure', 'text', 0, 1, 'internal', 212],
|
||||
['animal_medical_visit', 'oxygen', 'Oxygen', 'text', 0, 1, 'internal', 213],
|
||||
['animal_medical_visit', 'chronic_conditions', 'Chronic Conditions', 'textarea', 0, 1, 'internal', 214],
|
||||
['animal_medical_visit', 'acute_conditions', 'Acute Conditions', 'textarea', 0, 1, 'internal', 215],
|
||||
['animal_medical_visit', 'special_needs', 'Special Needs', 'textarea', 0, 1, 'internal', 216],
|
||||
['animal_medical_visit', 'date_of_most_recent_medical_visit', 'Date of Most Recent Medical Visit', 'date', 0, 1, 'internal', 217],
|
||||
|
||||
['animal_medical_visit', 'physician_name', 'Physician Name', 'text', 0, 1, 'internal', 218],
|
||||
['animal_medical_visit', 'physician_business_name', 'Physician Business Name', 'text', 0, 1, 'internal', 219],
|
||||
['animal_medical_visit', 'physician_street_address', 'Physician Street Address', 'text', 0, 1, 'internal', 220],
|
||||
['animal_medical_visit', 'physician_city', 'Physician City', 'text', 0, 1, 'internal', 221],
|
||||
['animal_medical_visit', 'physician_state', 'Physician State', 'text', 0, 1, 'internal', 222],
|
||||
['animal_medical_visit', 'physician_zip', 'Physician Zip', 'text', 0, 1, 'internal', 223],
|
||||
['animal_medical_visit', 'physician_cell_phone', 'Physician Cell Phone', 'text', 0, 1, 'internal', 224],
|
||||
['animal_medical_visit', 'physician_business_phone', 'Physician Business Phone', 'text', 0, 1, 'internal', 225],
|
||||
['animal_medical_visit', 'physician_license_number', 'Physician License Number', 'text', 0, 1, 'internal', 226],
|
||||
|
||||
['animal_medical_visit', 'second_physician_name', 'Second Physician Name', 'text', 0, 1, 'internal', 227],
|
||||
['animal_medical_visit', 'second_physician_business_name', 'Second Physician Business Name', 'text', 0, 1, 'internal', 228],
|
||||
['animal_medical_visit', 'second_physician_street_address', 'Second Physician Street Address', 'text', 0, 1, 'internal', 229],
|
||||
['animal_medical_visit', 'second_physician_city', 'Second Physician City', 'text', 0, 1, 'internal', 230],
|
||||
['animal_medical_visit', 'second_physician_state', 'Second Physician State', 'text', 0, 1, 'internal', 231],
|
||||
['animal_medical_visit', 'second_physician_zip', 'Second Physician Zip', 'text', 0, 1, 'internal', 232],
|
||||
['animal_medical_visit', 'second_physician_cell_phone', 'Second Physician Cell Phone', 'text', 0, 1, 'internal', 233],
|
||||
['animal_medical_visit', 'second_physician_business_phone', 'Second Physician Business Phone', 'text', 0, 1, 'internal', 234],
|
||||
['animal_medical_visit', 'second_physician_license_number', 'Second Physician License Number', 'text', 0, 1, 'internal', 235],
|
||||
|
||||
['animal_medical_visit', 'previous_physicians', 'Previous Physician(s)', 'textarea', 0, 1, 'internal', 236],
|
||||
|
||||
// Progress defaults from requirements
|
||||
['animal_progress_update', 'progress_notes', 'Notes', 'textarea', 0, 1, 'internal', 310],
|
||||
['animal_progress_update', 'routine_updates', 'Updates', 'textarea', 0, 1, 'internal', 311],
|
||||
['animal_progress_update', 'media_reference', 'Media', 'text', 0, 1, 'internal', 312],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
[$groupKey, $fieldKey, $label, $inputType, $required, $isActive, $visibility, $sortOrder] = $row;
|
||||
|
||||
$exists = (new \yii\db\Query())
|
||||
->from('rescue_field_definition')
|
||||
->where(['module_id' => 'animal_management', 'field_key' => $fieldKey])
|
||||
->exists($this->db);
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insert('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => $groupKey,
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $label,
|
||||
'input_type' => $inputType,
|
||||
'required' => $required,
|
||||
'is_core' => 0,
|
||||
'is_active' => $isActive,
|
||||
'visibility' => $visibility,
|
||||
'options' => '{}',
|
||||
'sort_order' => $sortOrder,
|
||||
'created_at' => $createdAt,
|
||||
'updated_at' => $createdAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldKeys = [
|
||||
'dob',
|
||||
'age',
|
||||
'previous_owner_user_id',
|
||||
'previous_owner_name',
|
||||
'previous_owner_business_name',
|
||||
'previous_owner_street_address',
|
||||
'previous_owner_city',
|
||||
'previous_owner_state',
|
||||
'previous_owner_zip',
|
||||
'previous_owner_cell_phone',
|
||||
'previous_owner_business_phone',
|
||||
'previous_owner_email',
|
||||
'lineage',
|
||||
'backstory',
|
||||
'rescue',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
'progress_notes',
|
||||
'routine_updates',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$this->delete('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'field_key' => $fieldKeys,
|
||||
'is_core' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
66
migrations/m260402_060000_create_animal_gallery.php
Normal file
66
migrations/m260402_060000_create_animal_gallery.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_060000_create_animal_gallery extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeCreateTable('rescue_animal_gallery_item', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'file_path' => $this->string(500)->null(),
|
||||
'file_id' => $this->integer()->null(),
|
||||
'source_post_id' => $this->integer()->null(),
|
||||
'source_type' => $this->string(32)->notNull()->defaultValue('upload'),
|
||||
'caption' => $this->text()->null(),
|
||||
'created_by' => $this->integer()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_animal', 'rescue_animal_gallery_item', 'animal_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_file', 'rescue_animal_gallery_item', 'file_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_post', 'rescue_animal_gallery_item', 'source_post_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_created', 'rescue_animal_gallery_item', ['animal_id', 'id'], false);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_gallery_animal',
|
||||
'rescue_animal_gallery_item',
|
||||
'animal_id',
|
||||
'rescue_animal',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('file', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_gallery_file',
|
||||
'rescue_animal_gallery_item',
|
||||
'file_id',
|
||||
'file',
|
||||
'id',
|
||||
'SET NULL',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('post', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_gallery_post',
|
||||
'rescue_animal_gallery_item',
|
||||
'source_post_id',
|
||||
'post',
|
||||
'id',
|
||||
'SET NULL',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_gallery_item');
|
||||
}
|
||||
}
|
||||
133
migrations/m260402_070000_lock_requirement_default_fields.php
Normal file
133
migrations/m260402_070000_lock_requirement_default_fields.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_070000_lock_requirement_default_fields extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldKeys = [
|
||||
'dob',
|
||||
'age',
|
||||
'previous_owner_user_id',
|
||||
'previous_owner_name',
|
||||
'previous_owner_business_name',
|
||||
'previous_owner_street_address',
|
||||
'previous_owner_city',
|
||||
'previous_owner_state',
|
||||
'previous_owner_zip',
|
||||
'previous_owner_cell_phone',
|
||||
'previous_owner_business_phone',
|
||||
'previous_owner_email',
|
||||
'lineage',
|
||||
'backstory',
|
||||
'rescue',
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
'progress_notes',
|
||||
'routine_updates',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$this->update('rescue_field_definition', [
|
||||
'is_core' => 1,
|
||||
'is_active' => 1,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
], [
|
||||
'module_id' => 'animal_management',
|
||||
'field_key' => $fieldKeys,
|
||||
]);
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldKeys = [
|
||||
'dob',
|
||||
'age',
|
||||
'previous_owner_user_id',
|
||||
'previous_owner_name',
|
||||
'previous_owner_business_name',
|
||||
'previous_owner_street_address',
|
||||
'previous_owner_city',
|
||||
'previous_owner_state',
|
||||
'previous_owner_zip',
|
||||
'previous_owner_cell_phone',
|
||||
'previous_owner_business_phone',
|
||||
'previous_owner_email',
|
||||
'lineage',
|
||||
'backstory',
|
||||
'rescue',
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
'progress_notes',
|
||||
'routine_updates',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$this->update('rescue_field_definition', [
|
||||
'is_core' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
], [
|
||||
'module_id' => 'animal_management',
|
||||
'field_key' => $fieldKeys,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260402_080000_add_display_override_profile_fields extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$createdAt = date('Y-m-d H:i:s');
|
||||
$rows = [
|
||||
['tile_display_fields', 'Tile Display Fields', 250],
|
||||
['hero_display_fields', 'Hero Display Fields', 251],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
[$fieldKey, $label, $sortOrder] = $row;
|
||||
|
||||
$existingId = (new \yii\db\Query())
|
||||
->select('id')
|
||||
->from('rescue_field_definition')
|
||||
->where([
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $fieldKey,
|
||||
])
|
||||
->scalar($this->db);
|
||||
|
||||
if ($existingId) {
|
||||
$this->update('rescue_field_definition', [
|
||||
'label' => $label,
|
||||
'input_type' => 'text',
|
||||
'required' => 0,
|
||||
'is_core' => 1,
|
||||
'is_active' => 1,
|
||||
'visibility' => 'internal',
|
||||
'sort_order' => $sortOrder,
|
||||
'updated_at' => $createdAt,
|
||||
], ['id' => (int)$existingId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insert('rescue_field_definition', [
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $label,
|
||||
'input_type' => 'text',
|
||||
'required' => 0,
|
||||
'is_core' => 1,
|
||||
'is_active' => 1,
|
||||
'visibility' => 'internal',
|
||||
'options' => '{}',
|
||||
'sort_order' => $sortOrder,
|
||||
'created_at' => $createdAt,
|
||||
'updated_at' => $createdAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->update('rescue_field_definition', [
|
||||
'is_core' => 0,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
], [
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => ['tile_display_fields', 'hero_display_fields'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
55
migrations/m260403_120000_create_animal_gallery_link.php
Normal file
55
migrations/m260403_120000_create_animal_gallery_link.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260403_120000_create_animal_gallery_link extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_animal_gallery_link', true) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->safeCreateTable('rescue_animal_gallery_link', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'gallery_id' => $this->integer()->notNull(),
|
||||
'contentcontainer_id' => $this->integer()->notNull(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_link_animal', 'rescue_animal_gallery_link', 'animal_id', true);
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_link_gallery', 'rescue_animal_gallery_link', 'gallery_id', true);
|
||||
$this->safeCreateIndex('idx_rescue_animal_gallery_link_container', 'rescue_animal_gallery_link', 'contentcontainer_id', false);
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_animal', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_gallery_link_animal',
|
||||
'rescue_animal_gallery_link',
|
||||
'animal_id',
|
||||
'rescue_animal',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('gallery_gallery', true) !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_gallery_link_gallery',
|
||||
'rescue_animal_gallery_link',
|
||||
'gallery_id',
|
||||
'gallery_gallery',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropTable('rescue_animal_gallery_link');
|
||||
}
|
||||
}
|
||||
94
migrations/m260403_130000_add_medical_stream_and_flags.php
Normal file
94
migrations/m260403_130000_add_medical_stream_and_flags.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
|
||||
class m260403_130000_add_medical_stream_and_flags extends Migration
|
||||
{
|
||||
public function safeUp()
|
||||
{
|
||||
$medicalTable = $this->db->getSchema()->getTableSchema('rescue_animal_medical_visit', true);
|
||||
if ($medicalTable !== null) {
|
||||
if (!isset($medicalTable->columns['post_to_space_feed'])) {
|
||||
$this->addColumn('rescue_animal_medical_visit', 'post_to_space_feed', $this->boolean()->notNull()->defaultValue(0));
|
||||
}
|
||||
|
||||
if (!isset($medicalTable->columns['post_to_animal_feed'])) {
|
||||
$this->addColumn('rescue_animal_medical_visit', 'post_to_animal_feed', $this->boolean()->notNull()->defaultValue(1));
|
||||
}
|
||||
|
||||
$this->update('rescue_animal_medical_visit', ['post_to_space_feed' => 0], ['post_to_space_feed' => null]);
|
||||
$this->update('rescue_animal_medical_visit', ['post_to_animal_feed' => 1], ['post_to_animal_feed' => null]);
|
||||
}
|
||||
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_animal_stream_entry', true) === null) {
|
||||
$this->safeCreateTable('rescue_animal_stream_entry', [
|
||||
'id' => $this->primaryKey(),
|
||||
'animal_id' => $this->integer()->notNull(),
|
||||
'entry_type' => $this->string(32)->notNull(),
|
||||
'medical_visit_id' => $this->integer()->null(),
|
||||
'progress_update_id' => $this->integer()->null(),
|
||||
'created_at' => $this->dateTime()->null(),
|
||||
'updated_at' => $this->dateTime()->null(),
|
||||
]);
|
||||
|
||||
$this->safeCreateIndex('idx_rescue_animal_stream_entry_animal', 'rescue_animal_stream_entry', 'animal_id', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_stream_entry_type', 'rescue_animal_stream_entry', 'entry_type', false);
|
||||
$this->safeCreateIndex('idx_rescue_animal_stream_entry_medical', 'rescue_animal_stream_entry', 'medical_visit_id', true);
|
||||
$this->safeCreateIndex('idx_rescue_animal_stream_entry_progress', 'rescue_animal_stream_entry', 'progress_update_id', true);
|
||||
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_stream_entry_animal',
|
||||
'rescue_animal_stream_entry',
|
||||
'animal_id',
|
||||
'rescue_animal',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
|
||||
$medicalVisitTable = $this->db->getSchema()->getTableSchema('rescue_animal_medical_visit', true);
|
||||
if ($medicalVisitTable !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_stream_entry_medical',
|
||||
'rescue_animal_stream_entry',
|
||||
'medical_visit_id',
|
||||
'rescue_animal_medical_visit',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
$progressTable = $this->db->getSchema()->getTableSchema('rescue_animal_progress_update', true);
|
||||
if ($progressTable !== null) {
|
||||
$this->safeAddForeignKey(
|
||||
'fk_rescue_animal_stream_entry_progress',
|
||||
'rescue_animal_stream_entry',
|
||||
'progress_update_id',
|
||||
'rescue_animal_progress_update',
|
||||
'id',
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function safeDown()
|
||||
{
|
||||
if ($this->db->getSchema()->getTableSchema('rescue_animal_stream_entry', true) !== null) {
|
||||
$this->safeDropTable('rescue_animal_stream_entry');
|
||||
}
|
||||
|
||||
$medicalTable = $this->db->getSchema()->getTableSchema('rescue_animal_medical_visit', true);
|
||||
if ($medicalTable !== null) {
|
||||
if (isset($medicalTable->columns['post_to_space_feed'])) {
|
||||
$this->dropColumn('rescue_animal_medical_visit', 'post_to_space_feed');
|
||||
}
|
||||
|
||||
if (isset($medicalTable->columns['post_to_animal_feed'])) {
|
||||
$this->dropColumn('rescue_animal_medical_visit', 'post_to_animal_feed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
213
models/Animal.php
Normal file
213
models/Animal.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class Animal extends ActiveRecord
|
||||
{
|
||||
public const STATUS_INTAKE = 'intake';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_TRANSFER_PENDING = 'transfer_pending';
|
||||
public const STATUS_TRANSFERRED = 'transferred';
|
||||
public const STATUS_ADOPTED = 'adopted';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['contentcontainer_id', 'animal_uid'], 'required'],
|
||||
[['contentcontainer_id', 'previous_owner_user_id', 'in_possession'], 'integer'],
|
||||
[['public_summary', 'medical_notes'], 'string'],
|
||||
[['name', 'animal_uid'], 'string', 'max' => 190],
|
||||
[['species', 'breed', 'location_name'], 'string', 'max' => 120],
|
||||
[['sex', 'status'], 'string', 'max' => 32],
|
||||
[['city'], 'string', 'max' => 120],
|
||||
[['state'], 'string', 'max' => 2],
|
||||
[['zip'], 'string', 'max' => 10],
|
||||
[['animal_uid'], 'unique'],
|
||||
[['status'], 'in', 'range' => array_keys(self::statusOptions())],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeValidate()
|
||||
{
|
||||
if (!parent::beforeValidate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isNewRecord && empty($this->animal_uid)) {
|
||||
$this->animal_uid = $this->generateAnimalUid();
|
||||
}
|
||||
|
||||
if (empty($this->status)) {
|
||||
$this->status = self::STATUS_INTAKE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_INTAKE => Yii::t('AnimalManagementModule.base', 'Intake'),
|
||||
self::STATUS_ACTIVE => Yii::t('AnimalManagementModule.base', 'Active'),
|
||||
self::STATUS_TRANSFER_PENDING => Yii::t('AnimalManagementModule.base', 'Transfer Pending'),
|
||||
self::STATUS_TRANSFERRED => Yii::t('AnimalManagementModule.base', 'Transferred'),
|
||||
self::STATUS_ADOPTED => Yii::t('AnimalManagementModule.base', 'Adopted'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
$name = trim((string)$this->name);
|
||||
return $name !== '' ? $name : (string)$this->animal_uid;
|
||||
}
|
||||
|
||||
public function getTransfers()
|
||||
{
|
||||
return $this->hasMany(AnimalTransfer::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getMedicalVisits()
|
||||
{
|
||||
return $this->hasMany(AnimalMedicalVisit::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getProgressUpdates()
|
||||
{
|
||||
return $this->hasMany(AnimalProgressUpdate::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getTransferEvents()
|
||||
{
|
||||
return $this->hasMany(AnimalTransferEvent::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalFieldValue::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getGalleryItems()
|
||||
{
|
||||
return $this->hasMany(AnimalGalleryItem::class, ['animal_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
if (!$fieldValue instanceof AnimalFieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (int)$definition->is_core === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((string)$definition->group_key !== 'animal_profile') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array((string)$definition->field_key, [
|
||||
'cover_image_url',
|
||||
'profile_image_url',
|
||||
'photo_url',
|
||||
'image_url',
|
||||
'profile_image',
|
||||
'photo',
|
||||
'tile_display_fields',
|
||||
'hero_display_fields',
|
||||
], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
} elseif ($inputType === 'select') {
|
||||
$display = $this->mapSelectDisplayValue((string)$definition->options, $raw);
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
'visibility' => $visibility,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function mapSelectDisplayValue(string $options, string $raw): string
|
||||
{
|
||||
$options = trim($options);
|
||||
if ($options === '') {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
$decoded = Json::decode($options, true);
|
||||
if (!is_array($decoded)) {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
if (array_values($decoded) === $decoded) {
|
||||
return in_array($raw, $decoded, true) ? $raw : $raw;
|
||||
}
|
||||
|
||||
return isset($decoded[$raw]) ? (string)$decoded[$raw] : $raw;
|
||||
}
|
||||
|
||||
private function generateAnimalUid(): string
|
||||
{
|
||||
do {
|
||||
$candidate = 'ANI-' . date('Ymd') . '-' . strtoupper(substr(Yii::$app->security->generateRandomString(8), 0, 6));
|
||||
$exists = static::find()->where(['animal_uid' => $candidate])->exists();
|
||||
} while ($exists);
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
49
models/AnimalFieldValue.php
Normal file
49
models/AnimalFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'field_definition_id'], 'required'],
|
||||
[['animal_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['animal_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['animal_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
75
models/AnimalGalleryItem.php
Normal file
75
models/AnimalGalleryItem.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\file\models\File;
|
||||
|
||||
class AnimalGalleryItem extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_gallery_item';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'file_id', 'source_post_id', 'created_by'], 'integer'],
|
||||
[['caption'], 'string'],
|
||||
[['source_type'], 'string', 'max' => 32],
|
||||
[['file_path'], 'string', 'max' => 500],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
if (empty($this->source_type)) {
|
||||
$this->source_type = 'upload';
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return $this->hasOne(File::class, ['id' => 'file_id']);
|
||||
}
|
||||
|
||||
public function getImageUrl(): string
|
||||
{
|
||||
$path = trim((string)$this->file_path);
|
||||
if ($path !== '') {
|
||||
if (preg_match('/^https?:\/\//i', $path) || substr($path, 0, 1) === '/') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
if ($this->file_id) {
|
||||
$file = $this->file;
|
||||
if ($file instanceof File) {
|
||||
return (string)$file->getUrl([], false);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
43
models/AnimalGalleryLink.php
Normal file
43
models/AnimalGalleryLink.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
|
||||
class AnimalGalleryLink extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_gallery_link';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'gallery_id', 'contentcontainer_id'], 'required'],
|
||||
[['animal_id', 'gallery_id', 'contentcontainer_id'], 'integer'],
|
||||
[['animal_id'], 'unique'],
|
||||
[['gallery_id'], 'unique'],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
}
|
||||
94
models/AnimalMedicalVisit.php
Normal file
94
models/AnimalMedicalVisit.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalMedicalVisit extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_medical_visit';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'created_by'], 'integer'],
|
||||
[['visit_at'], 'safe'],
|
||||
[['notes', 'recommendations'], 'string'],
|
||||
[['provider_name'], 'string', 'max' => 190],
|
||||
];
|
||||
|
||||
$table = Yii::$app->db->schema->getTableSchema(static::tableName(), true);
|
||||
if ($table !== null && isset($table->columns['post_to_space_feed']) && isset($table->columns['post_to_animal_feed'])) {
|
||||
$rules[] = [['post_to_space_feed', 'post_to_animal_feed'], 'integer'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalMedicalVisitFieldValue::class, ['medical_visit_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (string)$definition->group_key !== 'animal_medical_visit') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
49
models/AnimalMedicalVisitFieldValue.php
Normal file
49
models/AnimalMedicalVisitFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalMedicalVisitFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_medical_visit_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['medical_visit_id', 'field_definition_id'], 'required'],
|
||||
[['medical_visit_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['medical_visit_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['medical_visit_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMedicalVisit()
|
||||
{
|
||||
return $this->hasOne(AnimalMedicalVisit::class, ['id' => 'medical_visit_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
87
models/AnimalProgressUpdate.php
Normal file
87
models/AnimalProgressUpdate.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalProgressUpdate extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_progress_update';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id'], 'required'],
|
||||
[['animal_id', 'created_by', 'post_to_space_feed', 'post_to_animal_feed'], 'integer'],
|
||||
[['update_at'], 'safe'],
|
||||
[['vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'string'],
|
||||
[['weight'], 'string', 'max' => 32],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFieldValues()
|
||||
{
|
||||
return $this->hasMany(AnimalProgressUpdateFieldValue::class, ['progress_update_id' => 'id']);
|
||||
}
|
||||
|
||||
public function getCustomFieldDisplayValues(bool $includeRestricted = false): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->getFieldValues()->with('fieldDefinition')->all() as $fieldValue) {
|
||||
$definition = $fieldValue->fieldDefinition;
|
||||
if ($definition === null || (string)$definition->module_id !== 'animal_management') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int)$definition->is_active !== 1 || (string)$definition->group_key !== 'animal_progress_update') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visibility = (string)$definition->visibility;
|
||||
if (!$includeRestricted && $visibility !== 'public') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw = trim((string)$fieldValue->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = $raw;
|
||||
$inputType = (string)$definition->input_type;
|
||||
if ($inputType === 'boolean') {
|
||||
$display = in_array(strtolower($raw), ['1', 'true', 'yes', 'on'], true)
|
||||
? Yii::t('AnimalManagementModule.base', 'Yes')
|
||||
: Yii::t('AnimalManagementModule.base', 'No');
|
||||
}
|
||||
|
||||
$values[] = [
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : (string)$definition->field_key,
|
||||
'value' => $display,
|
||||
];
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
49
models/AnimalProgressUpdateFieldValue.php
Normal file
49
models/AnimalProgressUpdateFieldValue.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
|
||||
class AnimalProgressUpdateFieldValue extends ActiveRecord
|
||||
{
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_progress_update_field_value';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['progress_update_id', 'field_definition_id'], 'required'],
|
||||
[['progress_update_id', 'field_definition_id'], 'integer'],
|
||||
[['value_text', 'value_json'], 'string'],
|
||||
[['progress_update_id', 'field_definition_id'], 'unique', 'targetAttribute' => ['progress_update_id', 'field_definition_id']],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getProgressUpdate()
|
||||
{
|
||||
return $this->hasOne(AnimalProgressUpdate::class, ['id' => 'progress_update_id']);
|
||||
}
|
||||
|
||||
public function getFieldDefinition()
|
||||
{
|
||||
return $this->hasOne(RescueFieldDefinition::class, ['id' => 'field_definition_id']);
|
||||
}
|
||||
}
|
||||
101
models/AnimalStreamEntry.php
Normal file
101
models/AnimalStreamEntry.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\modules\animal_management\widgets\stream\AnimalStreamEntryWallEntry;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use Yii;
|
||||
|
||||
class AnimalStreamEntry extends ContentActiveRecord
|
||||
{
|
||||
public const TYPE_MEDICAL = 'medical';
|
||||
public const TYPE_PROGRESS = 'progress';
|
||||
|
||||
public $wallEntryClass = AnimalStreamEntryWallEntry::class;
|
||||
public $moduleId = 'animal_management';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_stream_entry';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'entry_type'], 'required'],
|
||||
[['animal_id', 'medical_visit_id', 'progress_update_id'], 'integer'],
|
||||
[['entry_type'], 'string', 'max' => 32],
|
||||
[['entry_type'], 'in', 'range' => [self::TYPE_MEDICAL, self::TYPE_PROGRESS]],
|
||||
[['medical_visit_id', 'progress_update_id'], 'validateLinkedRecord'],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validateLinkedRecord(string $attribute, $params): void
|
||||
{
|
||||
if ((string)$this->entry_type === self::TYPE_MEDICAL) {
|
||||
if ((int)$this->medical_visit_id <= 0) {
|
||||
$this->addError('medical_visit_id', Yii::t('AnimalManagementModule.base', 'Medical visit reference is required.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((string)$this->entry_type === self::TYPE_PROGRESS) {
|
||||
if ((int)$this->progress_update_id <= 0) {
|
||||
$this->addError('progress_update_id', Yii::t('AnimalManagementModule.base', 'Progress update reference is required.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getMedicalVisit()
|
||||
{
|
||||
return $this->hasOne(AnimalMedicalVisit::class, ['id' => 'medical_visit_id']);
|
||||
}
|
||||
|
||||
public function getProgressUpdate()
|
||||
{
|
||||
return $this->hasOne(AnimalProgressUpdate::class, ['id' => 'progress_update_id']);
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'fa-paw';
|
||||
}
|
||||
|
||||
public function getContentName()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Animal stream update');
|
||||
}
|
||||
|
||||
public function getContentDescription()
|
||||
{
|
||||
$animal = $this->animal;
|
||||
$animalName = $animal instanceof Animal ? $animal->getDisplayName() : Yii::t('AnimalManagementModule.base', 'Animal');
|
||||
|
||||
if ((string)$this->entry_type === self::TYPE_MEDICAL) {
|
||||
return Yii::t('AnimalManagementModule.base', 'Medical visit for {animal}', ['{animal}' => $animalName]);
|
||||
}
|
||||
|
||||
return Yii::t('AnimalManagementModule.base', 'Progress update for {animal}', ['{animal}' => $animalName]);
|
||||
}
|
||||
}
|
||||
158
models/AnimalTransfer.php
Normal file
158
models/AnimalTransfer.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class AnimalTransfer extends ActiveRecord
|
||||
{
|
||||
public const STATUS_REQUESTED = 'requested';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_DECLINED = 'declined';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_transfer';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['animal_id', 'from_contentcontainer_id', 'to_contentcontainer_id', 'status'], 'required'],
|
||||
[['animal_id', 'from_contentcontainer_id', 'to_contentcontainer_id', 'requested_by'], 'integer'],
|
||||
[['request_message', 'conditions_text'], 'string'],
|
||||
[['status'], 'string', 'max' => 32],
|
||||
[['status'], 'in', 'range' => array_keys(self::statusOptions())],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($insert && empty($this->created_at)) {
|
||||
$this->created_at = $now;
|
||||
}
|
||||
|
||||
$this->updated_at = $now;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
self::STATUS_REQUESTED => Yii::t('AnimalManagementModule.base', 'Requested'),
|
||||
self::STATUS_ACCEPTED => Yii::t('AnimalManagementModule.base', 'Accepted'),
|
||||
self::STATUS_DECLINED => Yii::t('AnimalManagementModule.base', 'Declined'),
|
||||
self::STATUS_COMPLETED => Yii::t('AnimalManagementModule.base', 'Completed'),
|
||||
self::STATUS_CANCELLED => Yii::t('AnimalManagementModule.base', 'Cancelled'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAnimal()
|
||||
{
|
||||
return $this->hasOne(Animal::class, ['id' => 'animal_id']);
|
||||
}
|
||||
|
||||
public function getFromSpace(): ?Space
|
||||
{
|
||||
return Space::findOne(['contentcontainer_id' => $this->from_contentcontainer_id]);
|
||||
}
|
||||
|
||||
public function getToSpace(): ?Space
|
||||
{
|
||||
return Space::findOne(['contentcontainer_id' => $this->to_contentcontainer_id]);
|
||||
}
|
||||
|
||||
public function getAuditEvents()
|
||||
{
|
||||
return $this->hasMany(AnimalTransferEvent::class, ['transfer_id' => 'id']);
|
||||
}
|
||||
|
||||
public function markAccepted(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_ACCEPTED;
|
||||
$this->responded_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'responded_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_ACCEPTED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer request accepted.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markDeclined(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_DECLINED;
|
||||
$this->responded_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'responded_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_DECLINED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer request declined.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markCompleted(): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_COMPLETED;
|
||||
$this->completed_at = date('Y-m-d H:i:s');
|
||||
if (!$this->save(false, ['status', 'completed_at', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_COMPLETED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer completed.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function markCancelled(string $message = ''): bool
|
||||
{
|
||||
$fromStatus = $this->status;
|
||||
$this->status = self::STATUS_CANCELLED;
|
||||
|
||||
if (!$this->save(false, ['status', 'updated_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimalTransferEvent::log(
|
||||
$this,
|
||||
AnimalTransferEvent::EVENT_CANCELLED,
|
||||
$fromStatus,
|
||||
$this->status,
|
||||
$message !== '' ? $message : Yii::t('AnimalManagementModule.base', 'Transfer cancelled.')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
82
models/AnimalTransferEvent.php
Normal file
82
models/AnimalTransferEvent.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class AnimalTransferEvent extends ActiveRecord
|
||||
{
|
||||
public const EVENT_REQUESTED = 'requested';
|
||||
public const EVENT_ACCEPTED = 'accepted';
|
||||
public const EVENT_DECLINED = 'declined';
|
||||
public const EVENT_COMPLETED = 'completed';
|
||||
public const EVENT_CANCELLED = 'cancelled';
|
||||
|
||||
public static function tableName()
|
||||
{
|
||||
return 'rescue_animal_transfer_event';
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['transfer_id', 'animal_id', 'event_type'], 'required'],
|
||||
[['transfer_id', 'animal_id', 'created_by'], 'integer'],
|
||||
[['message', 'metadata_json'], 'string'],
|
||||
[['event_type', 'from_status', 'to_status'], 'string', 'max' => 32],
|
||||
[['event_type'], 'in', 'range' => [
|
||||
self::EVENT_REQUESTED,
|
||||
self::EVENT_ACCEPTED,
|
||||
self::EVENT_DECLINED,
|
||||
self::EVENT_COMPLETED,
|
||||
self::EVENT_CANCELLED,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeSave($insert)
|
||||
{
|
||||
if (!$insert) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent::beforeSave($insert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->created_at)) {
|
||||
$this->created_at = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function log(
|
||||
AnimalTransfer $transfer,
|
||||
string $eventType,
|
||||
?string $fromStatus,
|
||||
?string $toStatus,
|
||||
string $message = '',
|
||||
array $metadata = []
|
||||
): bool {
|
||||
$event = new self();
|
||||
$event->transfer_id = (int)$transfer->id;
|
||||
$event->animal_id = (int)$transfer->animal_id;
|
||||
$event->event_type = $eventType;
|
||||
$event->from_status = $fromStatus;
|
||||
$event->to_status = $toStatus;
|
||||
$event->message = $message;
|
||||
$event->metadata_json = empty($metadata) ? null : Json::encode($metadata);
|
||||
$event->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
|
||||
return $event->save(false);
|
||||
}
|
||||
|
||||
public function getCreatedByUser()
|
||||
{
|
||||
return $this->hasOne(User::class, ['id' => 'created_by']);
|
||||
}
|
||||
}
|
||||
1024
models/forms/AnimalForm.php
Normal file
1024
models/forms/AnimalForm.php
Normal file
File diff suppressed because it is too large
Load Diff
346
models/forms/AnimalMedicalVisitForm.php
Normal file
346
models/forms/AnimalMedicalVisitForm.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisitFieldValue;
|
||||
use humhub\modules\animal_management\services\AnimalStreamPublisherService;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class AnimalMedicalVisitForm extends Model
|
||||
{
|
||||
public const MODULE_ID = 'animal_management';
|
||||
|
||||
public Animal $animal;
|
||||
|
||||
public string $visit_at = '';
|
||||
public string $provider_name = '';
|
||||
public string $notes = '';
|
||||
public string $recommendations = '';
|
||||
public bool $post_to_space_feed = false;
|
||||
public bool $post_to_animal_feed = true;
|
||||
public array $customFields = [];
|
||||
public ?AnimalMedicalVisit $medicalVisit = null;
|
||||
|
||||
private array $customFieldDefinitions = [];
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->bootstrapCustomFieldDefinitions();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['visit_at', 'provider_name', 'notes', 'recommendations'], 'safe'],
|
||||
[['provider_name'], 'string', 'max' => 190],
|
||||
[['notes', 'recommendations'], 'string'],
|
||||
[['post_to_space_feed', 'post_to_animal_feed'], 'boolean'],
|
||||
[['customFields'], 'safe'],
|
||||
[['customFields'], 'validateCustomFieldValues'],
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = $this->medicalVisit instanceof AnimalMedicalVisit
|
||||
? $this->medicalVisit
|
||||
: new AnimalMedicalVisit();
|
||||
|
||||
$isNew = $record->getIsNewRecord();
|
||||
|
||||
if ($isNew) {
|
||||
$record->animal_id = (int)$this->animal->id;
|
||||
$record->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
}
|
||||
|
||||
$record->visit_at = $this->normalizeDateTime($this->visit_at);
|
||||
$record->provider_name = trim($this->provider_name);
|
||||
$record->notes = trim($this->notes);
|
||||
$record->recommendations = trim($this->recommendations);
|
||||
if ($record->hasAttribute('post_to_space_feed') && $record->hasAttribute('post_to_animal_feed')) {
|
||||
$record->post_to_space_feed = $this->post_to_space_feed ? 1 : 0;
|
||||
$record->post_to_animal_feed = $this->post_to_animal_feed ? 1 : 0;
|
||||
}
|
||||
|
||||
if (!$record->save()) {
|
||||
$this->addErrors($record->getErrors());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->medicalVisit = $record;
|
||||
|
||||
if (!$this->saveCustomFieldValues($record)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->post_to_space_feed) {
|
||||
AnimalStreamPublisherService::publishMedicalVisit($this->animal, $record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setMedicalVisit(AnimalMedicalVisit $medicalVisit): void
|
||||
{
|
||||
$this->medicalVisit = $medicalVisit;
|
||||
$this->visit_at = (string)$medicalVisit->visit_at;
|
||||
$this->provider_name = (string)$medicalVisit->provider_name;
|
||||
$this->notes = (string)$medicalVisit->notes;
|
||||
$this->recommendations = (string)$medicalVisit->recommendations;
|
||||
if ($medicalVisit->hasAttribute('post_to_space_feed') && $medicalVisit->hasAttribute('post_to_animal_feed')) {
|
||||
$this->post_to_space_feed = ((int)$medicalVisit->post_to_space_feed) === 1;
|
||||
$this->post_to_animal_feed = ((int)$medicalVisit->post_to_animal_feed) === 1;
|
||||
} else {
|
||||
$this->post_to_space_feed = false;
|
||||
$this->post_to_animal_feed = true;
|
||||
}
|
||||
|
||||
$this->loadCustomFieldValues($medicalVisit);
|
||||
}
|
||||
|
||||
public function getCustomFieldDefinitions(): array
|
||||
{
|
||||
return $this->customFieldDefinitions;
|
||||
}
|
||||
|
||||
public function getCustomFieldSelectOptions(string $fieldKey): array
|
||||
{
|
||||
if (!isset($this->customFieldDefinitions[$fieldKey])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->parseSelectOptions((string)$this->customFieldDefinitions[$fieldKey]['options']);
|
||||
}
|
||||
|
||||
public function validateCustomFieldValues(string $attribute): void
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$value = $this->customFields[$fieldKey] ?? null;
|
||||
$label = (string)$definition['label'];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$required = ((int)$definition['required']) === 1;
|
||||
|
||||
if ($required && $this->isCustomValueEmpty($value, $inputType)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} is required.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isCustomValueEmpty($value, $inputType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized = is_array($value) ? '' : trim((string)$value);
|
||||
|
||||
if ($inputType === 'number' && !is_numeric($normalized)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid number.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($inputType === 'date' || $inputType === 'datetime') && strtotime($normalized) === false) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid date/time.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($inputType === 'select') {
|
||||
$options = $this->getCustomFieldSelectOptions($fieldKey);
|
||||
if (!empty($options) && !array_key_exists($normalized, $options)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Invalid selection for {field}.', ['{field}' => $label]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeDateTime(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp === false) {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return date('Y-m-d H:i:s', $timestamp);
|
||||
}
|
||||
|
||||
private function bootstrapCustomFieldDefinitions(): void
|
||||
{
|
||||
if (!class_exists(RescueFieldDefinition::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definitions = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID, 'group_key' => 'animal_medical_visit', 'is_active' => 1])
|
||||
->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])
|
||||
->all();
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$fieldKey = (string)$definition->field_key;
|
||||
$this->customFieldDefinitions[$fieldKey] = [
|
||||
'id' => (int)$definition->id,
|
||||
'field_key' => $fieldKey,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : $fieldKey,
|
||||
'input_type' => (string)$definition->input_type,
|
||||
'required' => (int)$definition->required,
|
||||
'visibility' => (string)$definition->visibility,
|
||||
'options' => (string)$definition->options,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function saveCustomFieldValues(AnimalMedicalVisit $record): bool
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$fieldDefinitionId = (int)$definition['id'];
|
||||
if ($fieldDefinitionId === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$rawValue = $this->customFields[$fieldKey] ?? null;
|
||||
$valueText = $this->normalizeCustomValue($rawValue, $inputType);
|
||||
|
||||
$valueRecord = AnimalMedicalVisitFieldValue::findOne([
|
||||
'medical_visit_id' => (int)$record->id,
|
||||
'field_definition_id' => $fieldDefinitionId,
|
||||
]);
|
||||
|
||||
if ($this->isCustomValueEmpty($valueText, $inputType)) {
|
||||
if ($valueRecord instanceof AnimalMedicalVisitFieldValue) {
|
||||
$valueRecord->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$valueRecord instanceof AnimalMedicalVisitFieldValue) {
|
||||
$valueRecord = new AnimalMedicalVisitFieldValue();
|
||||
$valueRecord->medical_visit_id = (int)$record->id;
|
||||
$valueRecord->field_definition_id = $fieldDefinitionId;
|
||||
}
|
||||
|
||||
$valueRecord->value_text = $valueText;
|
||||
$valueRecord->value_json = null;
|
||||
|
||||
if (!$valueRecord->save()) {
|
||||
$this->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not save custom medical field {field}.', [
|
||||
'{field}' => (string)$definition['label'],
|
||||
]));
|
||||
foreach ($valueRecord->getFirstErrors() as $error) {
|
||||
$this->addError('customFields', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$this->hasErrors('customFields');
|
||||
}
|
||||
|
||||
private function normalizeCustomValue($value, string $inputType): ?string
|
||||
{
|
||||
if ($inputType === 'boolean') {
|
||||
return !empty($value) ? '1' : '0';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return Json::encode($value);
|
||||
}
|
||||
|
||||
return trim((string)$value);
|
||||
}
|
||||
|
||||
private function isCustomValueEmpty($value, string $inputType): bool
|
||||
{
|
||||
if ($inputType === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return empty($value);
|
||||
}
|
||||
|
||||
return trim((string)$value) === '';
|
||||
}
|
||||
|
||||
private function parseSelectOptions(string $options): array
|
||||
{
|
||||
$options = trim($options);
|
||||
if ($options === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = null;
|
||||
try {
|
||||
$decoded = Json::decode($options, true);
|
||||
} catch (\Throwable $e) {
|
||||
$decoded = null;
|
||||
}
|
||||
|
||||
if (is_array($decoded)) {
|
||||
$result = [];
|
||||
if (array_values($decoded) === $decoded) {
|
||||
foreach ($decoded as $item) {
|
||||
$item = (string)$item;
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
$result[$item] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($decoded as $key => $value) {
|
||||
$key = (string)$key;
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$result[$key] = (string)$value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach (preg_split('/[\r\n,]+/', $options) as $item) {
|
||||
$item = trim((string)$item);
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$item] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function loadCustomFieldValues(AnimalMedicalVisit $record): void
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$value = AnimalMedicalVisitFieldValue::findOne([
|
||||
'medical_visit_id' => (int)$record->id,
|
||||
'field_definition_id' => (int)$definition['id'],
|
||||
]);
|
||||
|
||||
if (!$value instanceof AnimalMedicalVisitFieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->customFields[$fieldKey] = (string)$value->value_text;
|
||||
}
|
||||
}
|
||||
}
|
||||
348
models/forms/AnimalProgressUpdateForm.php
Normal file
348
models/forms/AnimalProgressUpdateForm.php
Normal file
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdateFieldValue;
|
||||
use humhub\modules\animal_management\services\AnimalStreamPublisherService;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class AnimalProgressUpdateForm extends Model
|
||||
{
|
||||
public const MODULE_ID = 'animal_management';
|
||||
|
||||
public Animal $animal;
|
||||
|
||||
public string $update_at = '';
|
||||
public string $weight = '';
|
||||
public string $vitals = '';
|
||||
public string $behavior_notes = '';
|
||||
public string $meal_plan_changes = '';
|
||||
public string $housing_changes = '';
|
||||
public string $medical_concerns = '';
|
||||
public bool $post_to_space_feed = false;
|
||||
public bool $post_to_animal_feed = true;
|
||||
public array $customFields = [];
|
||||
public ?AnimalProgressUpdate $progressUpdate = null;
|
||||
|
||||
private array $customFieldDefinitions = [];
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->bootstrapCustomFieldDefinitions();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['update_at', 'weight', 'vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'safe'],
|
||||
[['vitals', 'behavior_notes', 'meal_plan_changes', 'housing_changes', 'medical_concerns'], 'string'],
|
||||
[['weight'], 'string', 'max' => 32],
|
||||
[['post_to_space_feed', 'post_to_animal_feed'], 'boolean'],
|
||||
[['customFields'], 'safe'],
|
||||
[['customFields'], 'validateCustomFieldValues'],
|
||||
];
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = $this->progressUpdate instanceof AnimalProgressUpdate
|
||||
? $this->progressUpdate
|
||||
: new AnimalProgressUpdate();
|
||||
|
||||
$isNew = $record->getIsNewRecord();
|
||||
|
||||
if ($isNew) {
|
||||
$record->animal_id = (int)$this->animal->id;
|
||||
$record->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
}
|
||||
|
||||
$record->update_at = $this->normalizeDateTime($this->update_at);
|
||||
$record->weight = trim($this->weight);
|
||||
$record->vitals = trim($this->vitals);
|
||||
$record->behavior_notes = trim($this->behavior_notes);
|
||||
$record->meal_plan_changes = trim($this->meal_plan_changes);
|
||||
$record->housing_changes = trim($this->housing_changes);
|
||||
$record->medical_concerns = trim($this->medical_concerns);
|
||||
$record->post_to_space_feed = $this->post_to_space_feed ? 1 : 0;
|
||||
$record->post_to_animal_feed = $this->post_to_animal_feed ? 1 : 0;
|
||||
|
||||
if (!$record->save()) {
|
||||
$this->addErrors($record->getErrors());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->progressUpdate = $record;
|
||||
|
||||
if (!$this->saveCustomFieldValues($record)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($record->post_to_space_feed) {
|
||||
AnimalStreamPublisherService::publishProgressUpdate($this->animal, $record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setProgressUpdate(AnimalProgressUpdate $progressUpdate): void
|
||||
{
|
||||
$this->progressUpdate = $progressUpdate;
|
||||
$this->update_at = (string)$progressUpdate->update_at;
|
||||
$this->weight = (string)$progressUpdate->weight;
|
||||
$this->vitals = (string)$progressUpdate->vitals;
|
||||
$this->behavior_notes = (string)$progressUpdate->behavior_notes;
|
||||
$this->meal_plan_changes = (string)$progressUpdate->meal_plan_changes;
|
||||
$this->housing_changes = (string)$progressUpdate->housing_changes;
|
||||
$this->medical_concerns = (string)$progressUpdate->medical_concerns;
|
||||
$this->post_to_space_feed = ((int)$progressUpdate->post_to_space_feed) === 1;
|
||||
$this->post_to_animal_feed = ((int)$progressUpdate->post_to_animal_feed) === 1;
|
||||
|
||||
$this->loadCustomFieldValues($progressUpdate);
|
||||
}
|
||||
|
||||
public function getCustomFieldDefinitions(): array
|
||||
{
|
||||
return $this->customFieldDefinitions;
|
||||
}
|
||||
|
||||
public function getCustomFieldSelectOptions(string $fieldKey): array
|
||||
{
|
||||
if (!isset($this->customFieldDefinitions[$fieldKey])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->parseSelectOptions((string)$this->customFieldDefinitions[$fieldKey]['options']);
|
||||
}
|
||||
|
||||
public function validateCustomFieldValues(string $attribute): void
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$value = $this->customFields[$fieldKey] ?? null;
|
||||
$label = (string)$definition['label'];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$required = ((int)$definition['required']) === 1;
|
||||
|
||||
if ($required && $this->isCustomValueEmpty($value, $inputType)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} is required.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isCustomValueEmpty($value, $inputType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized = is_array($value) ? '' : trim((string)$value);
|
||||
|
||||
if ($inputType === 'number' && !is_numeric($normalized)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid number.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($inputType === 'date' || $inputType === 'datetime') && strtotime($normalized) === false) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid date/time.', ['{field}' => $label]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($inputType === 'select') {
|
||||
$options = $this->getCustomFieldSelectOptions($fieldKey);
|
||||
if (!empty($options) && !array_key_exists($normalized, $options)) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Invalid selection for {field}.', ['{field}' => $label]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeDateTime(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp === false) {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return date('Y-m-d H:i:s', $timestamp);
|
||||
}
|
||||
|
||||
private function bootstrapCustomFieldDefinitions(): void
|
||||
{
|
||||
if (!class_exists(RescueFieldDefinition::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definitions = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID, 'group_key' => 'animal_progress_update', 'is_active' => 1])
|
||||
->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])
|
||||
->all();
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$fieldKey = (string)$definition->field_key;
|
||||
$this->customFieldDefinitions[$fieldKey] = [
|
||||
'id' => (int)$definition->id,
|
||||
'field_key' => $fieldKey,
|
||||
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : $fieldKey,
|
||||
'input_type' => (string)$definition->input_type,
|
||||
'required' => (int)$definition->required,
|
||||
'visibility' => (string)$definition->visibility,
|
||||
'options' => (string)$definition->options,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function saveCustomFieldValues(AnimalProgressUpdate $record): bool
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$fieldDefinitionId = (int)$definition['id'];
|
||||
if ($fieldDefinitionId === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$rawValue = $this->customFields[$fieldKey] ?? null;
|
||||
$valueText = $this->normalizeCustomValue($rawValue, $inputType);
|
||||
|
||||
$valueRecord = AnimalProgressUpdateFieldValue::findOne([
|
||||
'progress_update_id' => (int)$record->id,
|
||||
'field_definition_id' => $fieldDefinitionId,
|
||||
]);
|
||||
|
||||
if ($this->isCustomValueEmpty($valueText, $inputType)) {
|
||||
if ($valueRecord instanceof AnimalProgressUpdateFieldValue) {
|
||||
$valueRecord->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$valueRecord instanceof AnimalProgressUpdateFieldValue) {
|
||||
$valueRecord = new AnimalProgressUpdateFieldValue();
|
||||
$valueRecord->progress_update_id = (int)$record->id;
|
||||
$valueRecord->field_definition_id = $fieldDefinitionId;
|
||||
}
|
||||
|
||||
$valueRecord->value_text = $valueText;
|
||||
$valueRecord->value_json = null;
|
||||
|
||||
if (!$valueRecord->save()) {
|
||||
$this->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not save custom progress field {field}.', [
|
||||
'{field}' => (string)$definition['label'],
|
||||
]));
|
||||
foreach ($valueRecord->getFirstErrors() as $error) {
|
||||
$this->addError('customFields', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$this->hasErrors('customFields');
|
||||
}
|
||||
|
||||
private function normalizeCustomValue($value, string $inputType): ?string
|
||||
{
|
||||
if ($inputType === 'boolean') {
|
||||
return !empty($value) ? '1' : '0';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return Json::encode($value);
|
||||
}
|
||||
|
||||
return trim((string)$value);
|
||||
}
|
||||
|
||||
private function isCustomValueEmpty($value, string $inputType): bool
|
||||
{
|
||||
if ($inputType === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return empty($value);
|
||||
}
|
||||
|
||||
return trim((string)$value) === '';
|
||||
}
|
||||
|
||||
private function parseSelectOptions(string $options): array
|
||||
{
|
||||
$options = trim($options);
|
||||
if ($options === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = null;
|
||||
try {
|
||||
$decoded = Json::decode($options, true);
|
||||
} catch (\Throwable $e) {
|
||||
$decoded = null;
|
||||
}
|
||||
|
||||
if (is_array($decoded)) {
|
||||
$result = [];
|
||||
if (array_values($decoded) === $decoded) {
|
||||
foreach ($decoded as $item) {
|
||||
$item = (string)$item;
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
$result[$item] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($decoded as $key => $value) {
|
||||
$key = (string)$key;
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$result[$key] = (string)$value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach (preg_split('/[\r\n,]+/', $options) as $item) {
|
||||
$item = trim((string)$item);
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$item] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function loadCustomFieldValues(AnimalProgressUpdate $record): void
|
||||
{
|
||||
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
|
||||
$value = AnimalProgressUpdateFieldValue::findOne([
|
||||
'progress_update_id' => (int)$record->id,
|
||||
'field_definition_id' => (int)$definition['id'],
|
||||
]);
|
||||
|
||||
if (!$value instanceof AnimalProgressUpdateFieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->customFields[$fieldKey] = (string)$value->value_text;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
models/forms/DisplaySettingsForm.php
Normal file
155
models/forms/DisplaySettingsForm.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class DisplaySettingsForm extends Model
|
||||
{
|
||||
public const DEFAULT_SEARCH_BLOCK_HEADING = 'Search Animal Profiles';
|
||||
|
||||
public const DEFAULT_TILE_FIELDS = [
|
||||
'name',
|
||||
'species',
|
||||
'status',
|
||||
];
|
||||
|
||||
public const DEFAULT_DETAIL_FIELDS = [
|
||||
'species',
|
||||
'breed',
|
||||
'sex',
|
||||
'status',
|
||||
'location_name',
|
||||
];
|
||||
|
||||
public ?ContentContainerActiveRecord $contentContainer = null;
|
||||
|
||||
public string $search_block_heading = self::DEFAULT_SEARCH_BLOCK_HEADING;
|
||||
public array $tile_fields = self::DEFAULT_TILE_FIELDS;
|
||||
public array $detail_fields = self::DEFAULT_DETAIL_FIELDS;
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['search_block_heading'], 'string', 'max' => 190],
|
||||
[['tile_fields', 'detail_fields'], 'each', 'rule' => ['in', 'range' => array_keys(self::fieldOptions())]],
|
||||
[['tile_fields', 'detail_fields'], 'default', 'value' => []],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'search_block_heading' => Yii::t('AnimalManagementModule.base', 'Search Block Heading'),
|
||||
'tile_fields' => Yii::t('AnimalManagementModule.base', 'Animal Tile Fields'),
|
||||
'detail_fields' => Yii::t('AnimalManagementModule.base', 'Animal Detail Hero Fields'),
|
||||
];
|
||||
}
|
||||
|
||||
public function loadValues(): void
|
||||
{
|
||||
if ($this->contentContainer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
||||
|
||||
$heading = trim((string)$settings->get('searchBlockHeading', self::DEFAULT_SEARCH_BLOCK_HEADING));
|
||||
$this->search_block_heading = $heading !== '' ? $heading : self::DEFAULT_SEARCH_BLOCK_HEADING;
|
||||
|
||||
$this->tile_fields = $this->normalizeFields(
|
||||
$settings->get('tileFields', Json::encode(self::DEFAULT_TILE_FIELDS)),
|
||||
self::DEFAULT_TILE_FIELDS
|
||||
);
|
||||
|
||||
$this->detail_fields = $this->normalizeFields(
|
||||
$settings->get('detailHeroFields', Json::encode(self::DEFAULT_DETAIL_FIELDS)),
|
||||
self::DEFAULT_DETAIL_FIELDS
|
||||
);
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if ($this->contentContainer === null) {
|
||||
$this->addError('search_block_heading', Yii::t('AnimalManagementModule.base', 'Missing content container.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
||||
|
||||
$heading = trim($this->search_block_heading);
|
||||
$settings->set('searchBlockHeading', $heading !== '' ? $heading : self::DEFAULT_SEARCH_BLOCK_HEADING);
|
||||
$settings->set('tileFields', Json::encode($this->normalizeFieldArray($this->tile_fields, self::DEFAULT_TILE_FIELDS)));
|
||||
$settings->set('detailHeroFields', Json::encode($this->normalizeFieldArray($this->detail_fields, self::DEFAULT_DETAIL_FIELDS)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function fieldOptions(): array
|
||||
{
|
||||
return [
|
||||
'name' => Yii::t('AnimalManagementModule.base', 'Name'),
|
||||
'species' => Yii::t('AnimalManagementModule.base', 'Species'),
|
||||
'breed' => Yii::t('AnimalManagementModule.base', 'Breed'),
|
||||
'sex' => Yii::t('AnimalManagementModule.base', 'Sex'),
|
||||
'status' => Yii::t('AnimalManagementModule.base', 'Status'),
|
||||
'location_name' => Yii::t('AnimalManagementModule.base', 'Location'),
|
||||
'animal_uid' => Yii::t('AnimalManagementModule.base', 'ID'),
|
||||
'last_medical' => Yii::t('AnimalManagementModule.base', 'Last Medical Visit'),
|
||||
'public_summary' => Yii::t('AnimalManagementModule.base', 'Public Summary'),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeFields($raw, array $default): array
|
||||
{
|
||||
$decoded = [];
|
||||
if (is_string($raw)) {
|
||||
$raw = trim($raw);
|
||||
if ($raw !== '') {
|
||||
try {
|
||||
$decoded = Json::decode($raw);
|
||||
} catch (\Throwable $e) {
|
||||
$decoded = [];
|
||||
}
|
||||
}
|
||||
} elseif (is_array($raw)) {
|
||||
$decoded = $raw;
|
||||
}
|
||||
|
||||
if (!is_array($decoded)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->normalizeFieldArray($decoded, $default);
|
||||
}
|
||||
|
||||
private function normalizeFieldArray(array $fields, array $default): array
|
||||
{
|
||||
$normalized = [];
|
||||
$options = self::fieldOptions();
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$field = trim((string)$field);
|
||||
if ($field === '' || !isset($options[$field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($field, $normalized, true)) {
|
||||
$normalized[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($normalized)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
335
models/forms/FieldDefinitionSettingsForm.php
Normal file
335
models/forms/FieldDefinitionSettingsForm.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class FieldDefinitionSettingsForm extends Model
|
||||
{
|
||||
public const MODULE_ID = 'animal_management';
|
||||
|
||||
public array $rows = [];
|
||||
|
||||
public string $new_field_key = '';
|
||||
public string $new_label = '';
|
||||
public string $new_input_type = 'text';
|
||||
public string $new_group_key = 'animal_profile';
|
||||
public string $new_visibility = 'public';
|
||||
public int $new_required = 0;
|
||||
public int $new_sort_order = 500;
|
||||
public string $new_options = '';
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['rows'], 'safe'],
|
||||
[['new_field_key', 'new_label', 'new_group_key', 'new_visibility', 'new_input_type', 'new_options'], 'string'],
|
||||
[['new_required', 'new_sort_order'], 'integer'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function inputTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
'text' => 'Text',
|
||||
'textarea' => 'Textarea',
|
||||
'boolean' => 'Boolean',
|
||||
'select' => 'Select',
|
||||
'number' => 'Number',
|
||||
'date' => 'Date',
|
||||
'datetime' => 'Date/Time',
|
||||
];
|
||||
}
|
||||
|
||||
public static function visibilityOptions(): array
|
||||
{
|
||||
return [
|
||||
'public' => 'Public',
|
||||
'private' => 'Private',
|
||||
'internal' => 'Internal',
|
||||
];
|
||||
}
|
||||
|
||||
public function loadRows(): void
|
||||
{
|
||||
if (!$this->canUseFieldDefinition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->rows = [];
|
||||
|
||||
$definitions = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID])
|
||||
->orderBy(['group_key' => SORT_ASC, 'sort_order' => SORT_ASC, 'id' => SORT_ASC])
|
||||
->all();
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$this->rows[] = [
|
||||
'id' => (int)$definition->id,
|
||||
'field_key' => (string)$definition->field_key,
|
||||
'label' => (string)$definition->label,
|
||||
'group_key' => (string)$definition->group_key,
|
||||
'input_type' => (string)$definition->input_type,
|
||||
'required' => (int)$definition->required,
|
||||
'is_core' => (int)$definition->is_core,
|
||||
'is_active' => (int)$definition->is_active,
|
||||
'visibility' => (string)$definition->visibility,
|
||||
'sort_order' => (int)$definition->sort_order,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->canUseFieldDefinition()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field definition storage is unavailable.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->clearValidationErrors();
|
||||
|
||||
$byId = [];
|
||||
foreach (RescueFieldDefinition::find()->where(['module_id' => self::MODULE_ID])->all() as $definition) {
|
||||
$byId[(int)$definition->id] = $definition;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
try {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($this->rows as $index => $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id === 0 || !isset($byId[$id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition = $byId[$id];
|
||||
$isCore = ((int)$definition->is_core) === 1;
|
||||
$remove = !empty($row['remove']);
|
||||
|
||||
if ($remove && !$isCore) {
|
||||
if (!$definition->delete()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not remove field {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = trim((string)($row['label'] ?? ''));
|
||||
$groupKey = trim((string)($row['group_key'] ?? $definition->group_key));
|
||||
$visibility = trim((string)($row['visibility'] ?? $definition->visibility));
|
||||
$sortOrder = (int)($row['sort_order'] ?? $definition->sort_order);
|
||||
$required = !empty($row['required']) ? 1 : 0;
|
||||
$isActive = !empty($row['is_active']) ? 1 : 0;
|
||||
$inputType = trim((string)($row['input_type'] ?? $definition->input_type));
|
||||
$options = trim((string)$definition->options);
|
||||
|
||||
if ($label === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field label cannot be empty for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($groupKey === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Group key cannot be empty for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($visibility, self::visibilityOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid visibility for {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$isCore && !$this->validateSelectOptions($inputType, $options, (string)$definition->field_key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->label = $label;
|
||||
$definition->group_key = $groupKey;
|
||||
$definition->visibility = $visibility;
|
||||
$definition->sort_order = $sortOrder;
|
||||
$definition->required = $isCore ? (int)$definition->required : $required;
|
||||
$definition->is_active = $isCore ? (int)$definition->is_active : $isActive;
|
||||
$definition->updated_at = $now;
|
||||
|
||||
if (!$definition->save()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not save field {field}.', [
|
||||
'{field}' => (string)$definition->field_key,
|
||||
]));
|
||||
foreach ($definition->getFirstErrors() as $error) {
|
||||
$this->addError('rows', $error);
|
||||
}
|
||||
}
|
||||
|
||||
$this->rows[$index]['is_core'] = (int)$definition->is_core;
|
||||
}
|
||||
|
||||
if ($this->hasErrors()) {
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->saveNewField($now)) {
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction->commit();
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$transaction->rollBack();
|
||||
Yii::error($e, 'animal_management.field_definition_settings');
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Unexpected error while saving field settings.'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canUseFieldDefinition(): bool
|
||||
{
|
||||
if (!class_exists(RescueFieldDefinition::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) !== null;
|
||||
}
|
||||
|
||||
private function saveNewField(string $now): bool
|
||||
{
|
||||
$newFieldKey = strtolower(trim($this->new_field_key));
|
||||
$newLabel = trim($this->new_label);
|
||||
|
||||
if ($newFieldKey === '' && $newLabel === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($newFieldKey === '' || $newLabel === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Both new field key and label are required.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-z0-9_]+$/', $newFieldKey)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'New field key must contain only lowercase letters, numbers, and underscores.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($this->new_input_type, self::inputTypeOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid input type for new field.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists($this->new_visibility, self::visibilityOptions())) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Invalid visibility for new field.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupKey = trim($this->new_group_key);
|
||||
if ($groupKey === '') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'New field group key is required.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = trim($this->new_options);
|
||||
if (!$this->validateSelectOptions($this->new_input_type, $options, $newFieldKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$exists = RescueFieldDefinition::find()
|
||||
->where(['module_id' => self::MODULE_ID, 'field_key' => $newFieldKey])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Field key {key} already exists.', ['{key}' => $newFieldKey]));
|
||||
return false;
|
||||
}
|
||||
|
||||
$field = new RescueFieldDefinition();
|
||||
$field->module_id = self::MODULE_ID;
|
||||
$field->group_key = $groupKey;
|
||||
$field->field_key = $newFieldKey;
|
||||
$field->label = $newLabel;
|
||||
$field->input_type = $this->new_input_type;
|
||||
$field->required = $this->new_required ? 1 : 0;
|
||||
$field->is_core = 0;
|
||||
$field->is_active = 1;
|
||||
$field->visibility = $this->new_visibility;
|
||||
$field->sort_order = (int)$this->new_sort_order;
|
||||
$field->options = $options !== '' ? $options : '{}';
|
||||
$field->created_at = $now;
|
||||
$field->updated_at = $now;
|
||||
|
||||
if (!$field->save()) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Could not create new field.'));
|
||||
foreach ($field->getFirstErrors() as $error) {
|
||||
$this->addError('rows', $error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->new_field_key = '';
|
||||
$this->new_label = '';
|
||||
$this->new_input_type = 'text';
|
||||
$this->new_group_key = 'animal_profile';
|
||||
$this->new_visibility = 'public';
|
||||
$this->new_required = 0;
|
||||
$this->new_sort_order = 500;
|
||||
$this->new_options = '';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function clearValidationErrors(): void
|
||||
{
|
||||
foreach (array_keys($this->errors) as $attribute) {
|
||||
$this->clearErrors($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSelectOptions(string $inputType, string $options, string $fieldKey): bool
|
||||
{
|
||||
if ($inputType !== 'select') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($options === '' || $options === '{}') {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} requires options JSON or comma/newline list.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
$decoded = json_decode($options, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
if (!is_array($decoded) || empty($decoded)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} options JSON must be a non-empty array/object.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$parts = preg_split('/[\r\n,]+/', $options);
|
||||
$parts = array_filter(array_map(static function ($item) {
|
||||
return trim((string)$item);
|
||||
}, $parts));
|
||||
|
||||
if (empty($parts)) {
|
||||
$this->addError('rows', Yii::t('AnimalManagementModule.base', 'Select field {field} requires at least one option.', [
|
||||
'{field}' => $fieldKey,
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
165
models/forms/TransferRequestForm.php
Normal file
165
models/forms/TransferRequestForm.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\models\forms;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\animal_management\models\AnimalTransferEvent;
|
||||
use humhub\modules\animal_management\notifications\TransferNotifier;
|
||||
use humhub\modules\animal_management\notifications\TransferRequestedNotification;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class TransferRequestForm extends Model
|
||||
{
|
||||
public Animal $animal;
|
||||
public Space $sourceSpace;
|
||||
|
||||
public int $to_space_id = 0;
|
||||
public string $request_message = '';
|
||||
public string $conditions_text = '';
|
||||
|
||||
private ?AnimalTransfer $transfer = null;
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
[['to_space_id'], 'required'],
|
||||
[['to_space_id'], 'integer'],
|
||||
[['request_message', 'conditions_text'], 'string'],
|
||||
[['to_space_id'], 'validateTargetSpace'],
|
||||
];
|
||||
}
|
||||
|
||||
public function validateTargetSpace($attribute): void
|
||||
{
|
||||
if ($this->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->to_space_id === (int)$this->sourceSpace->id) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'You must choose a different destination rescue.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$target = Space::findOne(['id' => $this->to_space_id, 'status' => Space::STATUS_ENABLED]);
|
||||
if (!$target instanceof Space) {
|
||||
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Selected destination rescue was not found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getTargetOptions(): array
|
||||
{
|
||||
return Space::find()
|
||||
->select(['name', 'id'])
|
||||
->where(['status' => Space::STATUS_ENABLED])
|
||||
->andWhere(['!=', 'id', (int)$this->sourceSpace->id])
|
||||
->orderBy(['name' => SORT_ASC])
|
||||
->indexBy('id')
|
||||
->column();
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetSpace = Space::findOne(['id' => $this->to_space_id, 'status' => Space::STATUS_ENABLED]);
|
||||
if (!$targetSpace instanceof Space) {
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Selected destination rescue was not found.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
try {
|
||||
$transfer = new AnimalTransfer();
|
||||
$transfer->animal_id = (int)$this->animal->id;
|
||||
$transfer->from_contentcontainer_id = (int)$this->sourceSpace->contentcontainer_id;
|
||||
$transfer->to_contentcontainer_id = (int)$targetSpace->contentcontainer_id;
|
||||
$transfer->requested_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
|
||||
$transfer->status = AnimalTransfer::STATUS_REQUESTED;
|
||||
$transfer->request_message = trim($this->request_message);
|
||||
$transfer->conditions_text = trim($this->conditions_text);
|
||||
|
||||
if (!$transfer->save()) {
|
||||
$this->addErrors($transfer->getErrors());
|
||||
$transaction->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->animal->status = Animal::STATUS_TRANSFER_PENDING;
|
||||
if (!$this->animal->save(false, ['status', 'updated_at'])) {
|
||||
$transaction->rollBack();
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not update animal transfer status.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$auditSaved = AnimalTransferEvent::log(
|
||||
$transfer,
|
||||
AnimalTransferEvent::EVENT_REQUESTED,
|
||||
null,
|
||||
AnimalTransfer::STATUS_REQUESTED,
|
||||
Yii::t('AnimalManagementModule.base', 'Transfer requested from {from} to {to}.', [
|
||||
'{from}' => (string)$this->sourceSpace->name,
|
||||
'{to}' => (string)$targetSpace->name,
|
||||
]),
|
||||
[
|
||||
'from_space_id' => (int)$this->sourceSpace->id,
|
||||
'to_space_id' => (int)$targetSpace->id,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$auditSaved) {
|
||||
$transaction->rollBack();
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not write transfer audit log.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$transaction->rollBack();
|
||||
Yii::error($e, 'animal_management.transfer_request');
|
||||
$this->addError('to_space_id', Yii::t('AnimalManagementModule.base', 'Could not create transfer request.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->transfer = $transfer;
|
||||
$this->notifyTargetManagers();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notifyTargetManagers(): void
|
||||
{
|
||||
$targetSpace = Space::findOne(['id' => $this->to_space_id]);
|
||||
if (!$targetSpace instanceof Space) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recipients = TransferNotifier::privilegedUsersForSpace($targetSpace);
|
||||
|
||||
$originator = Yii::$app->user->isGuest ? null : Yii::$app->user->getIdentity();
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
if ($originator instanceof \humhub\modules\user\models\User && (int)$recipient->id === (int)$originator->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification = TransferRequestedNotification::instance();
|
||||
if ($originator) {
|
||||
$notification->from($originator);
|
||||
}
|
||||
$notification->animalName = $this->animal->getDisplayName();
|
||||
$notification->fromSpaceName = (string)$this->sourceSpace->name;
|
||||
$notification->toSpaceGuid = (string)$targetSpace->guid;
|
||||
$notification->payload([
|
||||
'animalName' => $notification->animalName,
|
||||
'fromSpaceName' => $notification->fromSpaceName,
|
||||
'toSpaceGuid' => $notification->toSpaceGuid,
|
||||
]);
|
||||
$notification->send($recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
module.json
Normal file
15
module.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "animal_management",
|
||||
"name": "Animal Management",
|
||||
"description": "Animal intake, profile management, and transfer workflow foundation for rescue spaces.",
|
||||
"keywords": ["animals", "rescue", "intake", "transfer"],
|
||||
"version": "0.1.0",
|
||||
"humhub": {
|
||||
"minVersion": "1.14"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kelin Rescue Hub"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
notifications/TransferNotificationCategory.php
Normal file
21
notifications/TransferNotificationCategory.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\notifications;
|
||||
|
||||
use humhub\modules\notification\components\NotificationCategory;
|
||||
use Yii;
|
||||
|
||||
class TransferNotificationCategory extends NotificationCategory
|
||||
{
|
||||
public $id = 'animal_management_transfer';
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Animal Transfers');
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Receive notifications about animal transfer requests and status updates.');
|
||||
}
|
||||
}
|
||||
120
notifications/TransferNotifier.php
Normal file
120
notifications/TransferNotifier.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\notifications;
|
||||
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\modules\user\models\User;
|
||||
|
||||
class TransferNotifier
|
||||
{
|
||||
public static function notifyStatusChange(AnimalTransfer $transfer, string $eventType, ?User $originator = null, string $details = ''): int
|
||||
{
|
||||
$animal = $transfer->animal;
|
||||
if ($animal === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$fromSpace = $transfer->getFromSpace();
|
||||
$toSpace = $transfer->getToSpace();
|
||||
if (!$fromSpace instanceof Space || !$toSpace instanceof Space) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$targetSpaces = static::targetSpacesForEvent($eventType, $fromSpace, $toSpace);
|
||||
$sentCount = 0;
|
||||
$seenRecipients = [];
|
||||
|
||||
foreach ($targetSpaces as $space) {
|
||||
$recipients = static::privilegedUsersForSpace($space);
|
||||
foreach ($recipients as $recipient) {
|
||||
$recipientId = (int)$recipient->id;
|
||||
if ($originator instanceof User && $recipientId === (int)$originator->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($seenRecipients[$recipientId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seenRecipients[$recipientId] = true;
|
||||
$notification = TransferStatusNotification::instance();
|
||||
if ($originator instanceof User) {
|
||||
$notification->from($originator);
|
||||
}
|
||||
$notification->animalName = $animal->getDisplayName();
|
||||
$notification->fromSpaceName = (string)$fromSpace->name;
|
||||
$notification->toSpaceName = (string)$toSpace->name;
|
||||
$notification->spaceGuid = (string)$space->guid;
|
||||
$notification->eventType = $eventType;
|
||||
$notification->details = $details;
|
||||
$notification->payload([
|
||||
'animalName' => $notification->animalName,
|
||||
'fromSpaceName' => $notification->fromSpaceName,
|
||||
'toSpaceName' => $notification->toSpaceName,
|
||||
'spaceGuid' => $notification->spaceGuid,
|
||||
'eventType' => $notification->eventType,
|
||||
'details' => $notification->details,
|
||||
]);
|
||||
$notification->send($recipient);
|
||||
$sentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return $sentCount;
|
||||
}
|
||||
|
||||
public static function privilegedUsersForSpace(Space $space): array
|
||||
{
|
||||
$recipients = [];
|
||||
|
||||
foreach ($space->getPrivilegedGroupUsers() as $users) {
|
||||
foreach ($users as $user) {
|
||||
if ($user instanceof User && (int)$user->status === User::STATUS_ENABLED) {
|
||||
$recipients[(int)$user->id] = $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($recipients)) {
|
||||
$owner = $space->getOwnerUser()->one();
|
||||
if ($owner instanceof User && (int)$owner->status === User::STATUS_ENABLED) {
|
||||
$recipients[(int)$owner->id] = $owner;
|
||||
}
|
||||
}
|
||||
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
private static function targetSpacesForEvent(string $eventType, Space $fromSpace, Space $toSpace): array
|
||||
{
|
||||
$spaces = [];
|
||||
|
||||
switch ($eventType) {
|
||||
case 'accepted':
|
||||
case 'declined':
|
||||
$spaces[] = $fromSpace;
|
||||
break;
|
||||
case 'cancelled':
|
||||
$spaces[] = $toSpace;
|
||||
break;
|
||||
case 'completed':
|
||||
$spaces[] = $fromSpace;
|
||||
break;
|
||||
default:
|
||||
$spaces[] = $fromSpace;
|
||||
$spaces[] = $toSpace;
|
||||
break;
|
||||
}
|
||||
|
||||
$unique = [];
|
||||
foreach ($spaces as $space) {
|
||||
$key = (int)$space->contentcontainer_id;
|
||||
if (!isset($unique[$key])) {
|
||||
$unique[$key] = $space;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($unique);
|
||||
}
|
||||
}
|
||||
109
notifications/TransferRequestedNotification.php
Normal file
109
notifications/TransferRequestedNotification.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\notifications;
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\notification\components\BaseNotification;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
use yii\helpers\Url;
|
||||
|
||||
class TransferRequestedNotification extends BaseNotification
|
||||
{
|
||||
public $moduleId = 'animal_management';
|
||||
public $requireSource = false;
|
||||
public $requireOriginator = false;
|
||||
|
||||
public string $animalName = '';
|
||||
public string $fromSpaceName = '';
|
||||
public string $toSpaceGuid = '';
|
||||
|
||||
public function category()
|
||||
{
|
||||
return new TransferNotificationCategory();
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
$toSpaceGuid = $this->payloadString('toSpaceGuid', $this->toSpaceGuid);
|
||||
$space = Space::findOne(['guid' => $toSpaceGuid]);
|
||||
if ($space instanceof Space) {
|
||||
return $space->createUrl('/animal_management/animals/index') . '#incoming-transfers';
|
||||
}
|
||||
|
||||
return Url::to(['/animal_management/animals/index', 'sguid' => $toSpaceGuid]) . '#incoming-transfers';
|
||||
}
|
||||
|
||||
public function html()
|
||||
{
|
||||
$animalName = $this->payloadString('animalName', $this->animalName);
|
||||
|
||||
if ($this->originator) {
|
||||
return Yii::t('AnimalManagementModule.base', '{displayName} requested to transfer {animalName}.', [
|
||||
'displayName' => Html::tag('strong', Html::encode($this->originator->displayName)),
|
||||
'animalName' => Html::tag('strong', Html::encode($animalName)),
|
||||
]);
|
||||
}
|
||||
|
||||
return Yii::t('AnimalManagementModule.base', 'A transfer was requested for {animalName}.', [
|
||||
'animalName' => Html::tag('strong', Html::encode($animalName)),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMailSubject()
|
||||
{
|
||||
$animalName = $this->payloadString('animalName', $this->animalName);
|
||||
|
||||
return Yii::t('AnimalManagementModule.base', 'Animal transfer request: {animalName}', [
|
||||
'animalName' => $animalName,
|
||||
]);
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
$data = parent::__serialize();
|
||||
$data['animalName'] = $this->animalName;
|
||||
$data['fromSpaceName'] = $this->fromSpaceName;
|
||||
$data['toSpaceGuid'] = $this->toSpaceGuid;
|
||||
$data['payload'] = $this->payload;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function __unserialize($unserializedArr)
|
||||
{
|
||||
parent::__unserialize($unserializedArr);
|
||||
|
||||
$this->animalName = (string)($unserializedArr['animalName'] ?? '');
|
||||
$this->fromSpaceName = (string)($unserializedArr['fromSpaceName'] ?? '');
|
||||
$this->toSpaceGuid = (string)($unserializedArr['toSpaceGuid'] ?? '');
|
||||
|
||||
if (isset($unserializedArr['payload']) && is_array($unserializedArr['payload'])) {
|
||||
$this->payload = $unserializedArr['payload'];
|
||||
}
|
||||
}
|
||||
|
||||
private function payloadString(string $key, string $fallback = ''): string
|
||||
{
|
||||
if (is_array($this->payload) && array_key_exists($key, $this->payload)) {
|
||||
return trim((string)$this->payload[$key]);
|
||||
}
|
||||
|
||||
if ($this->record !== null && !empty($this->record->payload)) {
|
||||
try {
|
||||
$decoded = Json::decode((string)$this->record->payload);
|
||||
if (is_array($decoded)) {
|
||||
$this->payload = $decoded;
|
||||
if (array_key_exists($key, $decoded)) {
|
||||
return trim((string)$decoded[$key]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fall back to explicit property values when payload is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
return trim($fallback);
|
||||
}
|
||||
}
|
||||
162
notifications/TransferStatusNotification.php
Normal file
162
notifications/TransferStatusNotification.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\notifications;
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\notification\components\BaseNotification;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
use yii\helpers\Url;
|
||||
|
||||
class TransferStatusNotification extends BaseNotification
|
||||
{
|
||||
public $moduleId = 'animal_management';
|
||||
public $requireSource = false;
|
||||
public $requireOriginator = false;
|
||||
|
||||
public string $animalName = '';
|
||||
public string $fromSpaceName = '';
|
||||
public string $toSpaceName = '';
|
||||
public string $spaceGuid = '';
|
||||
public string $eventType = '';
|
||||
public string $details = '';
|
||||
|
||||
public function category()
|
||||
{
|
||||
return new TransferNotificationCategory();
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
$eventType = $this->payloadString('eventType', $this->eventType);
|
||||
$spaceGuid = $this->payloadString('spaceGuid', $this->spaceGuid);
|
||||
$anchor = static::eventAnchor($eventType);
|
||||
$space = Space::findOne(['guid' => $spaceGuid]);
|
||||
if ($space instanceof Space) {
|
||||
return $space->createUrl('/animal_management/animals/index') . '#' . $anchor;
|
||||
}
|
||||
|
||||
return Url::to(['/animal_management/animals/index', 'sguid' => $spaceGuid]) . '#' . $anchor;
|
||||
}
|
||||
|
||||
public function html()
|
||||
{
|
||||
$animalName = $this->payloadString('animalName', $this->animalName);
|
||||
$eventType = $this->payloadString('eventType', $this->eventType);
|
||||
$details = $this->payloadString('details', $this->details);
|
||||
|
||||
$params = [
|
||||
'animalName' => Html::tag('strong', Html::encode($animalName)),
|
||||
'status' => Html::encode(static::statusLabel($eventType)),
|
||||
];
|
||||
|
||||
if ($this->originator) {
|
||||
$params['displayName'] = Html::tag('strong', Html::encode($this->originator->displayName));
|
||||
$message = Yii::t('AnimalManagementModule.base', '{displayName} {status} the transfer of {animalName}.', $params);
|
||||
} else {
|
||||
$message = Yii::t('AnimalManagementModule.base', '{status} the transfer of {animalName}.', $params);
|
||||
}
|
||||
|
||||
if ($details !== '') {
|
||||
$message .= ' ' . Html::encode($details);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getMailSubject()
|
||||
{
|
||||
$animalName = $this->payloadString('animalName', $this->animalName);
|
||||
$eventType = $this->payloadString('eventType', $this->eventType);
|
||||
|
||||
return Yii::t('AnimalManagementModule.base', 'Animal transfer update: {animalName} ({status})', [
|
||||
'animalName' => $animalName,
|
||||
'status' => static::statusLabel($eventType),
|
||||
]);
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
$data = parent::__serialize();
|
||||
$data['animalName'] = $this->animalName;
|
||||
$data['fromSpaceName'] = $this->fromSpaceName;
|
||||
$data['toSpaceName'] = $this->toSpaceName;
|
||||
$data['spaceGuid'] = $this->spaceGuid;
|
||||
$data['eventType'] = $this->eventType;
|
||||
$data['details'] = $this->details;
|
||||
$data['payload'] = $this->payload;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function __unserialize($unserializedArr)
|
||||
{
|
||||
parent::__unserialize($unserializedArr);
|
||||
|
||||
$this->animalName = (string)($unserializedArr['animalName'] ?? '');
|
||||
$this->fromSpaceName = (string)($unserializedArr['fromSpaceName'] ?? '');
|
||||
$this->toSpaceName = (string)($unserializedArr['toSpaceName'] ?? '');
|
||||
$this->spaceGuid = (string)($unserializedArr['spaceGuid'] ?? '');
|
||||
$this->eventType = (string)($unserializedArr['eventType'] ?? '');
|
||||
$this->details = (string)($unserializedArr['details'] ?? '');
|
||||
|
||||
if (isset($unserializedArr['payload']) && is_array($unserializedArr['payload'])) {
|
||||
$this->payload = $unserializedArr['payload'];
|
||||
}
|
||||
}
|
||||
|
||||
public static function statusLabel(string $eventType): string
|
||||
{
|
||||
switch ($eventType) {
|
||||
case 'accepted':
|
||||
return Yii::t('AnimalManagementModule.base', 'Accepted');
|
||||
case 'declined':
|
||||
return Yii::t('AnimalManagementModule.base', 'Declined');
|
||||
case 'completed':
|
||||
return Yii::t('AnimalManagementModule.base', 'Completed');
|
||||
case 'cancelled':
|
||||
return Yii::t('AnimalManagementModule.base', 'Cancelled');
|
||||
default:
|
||||
return Yii::t('AnimalManagementModule.base', 'Updated');
|
||||
}
|
||||
}
|
||||
|
||||
private static function eventAnchor(string $eventType): string
|
||||
{
|
||||
switch ($eventType) {
|
||||
case 'requested':
|
||||
case 'cancelled':
|
||||
return 'incoming-transfers';
|
||||
case 'accepted':
|
||||
case 'declined':
|
||||
case 'completed':
|
||||
return 'outgoing-transfers';
|
||||
default:
|
||||
return 'outgoing-transfers';
|
||||
}
|
||||
}
|
||||
|
||||
private function payloadString(string $key, string $fallback = ''): string
|
||||
{
|
||||
if (is_array($this->payload) && array_key_exists($key, $this->payload)) {
|
||||
return trim((string)$this->payload[$key]);
|
||||
}
|
||||
|
||||
if ($this->record !== null && !empty($this->record->payload)) {
|
||||
try {
|
||||
$decoded = Json::decode((string)$this->record->payload);
|
||||
if (is_array($decoded)) {
|
||||
$this->payload = $decoded;
|
||||
if (array_key_exists($key, $decoded)) {
|
||||
return trim((string)$decoded[$key]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fall back to explicit property values when payload is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
return trim($fallback);
|
||||
}
|
||||
}
|
||||
33
permissions/ManageAnimals.php
Normal file
33
permissions/ManageAnimals.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\permissions;
|
||||
|
||||
use humhub\libs\BasePermission;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class ManageAnimals extends BasePermission
|
||||
{
|
||||
public $moduleId = 'animal_management';
|
||||
|
||||
public $defaultAllowedGroups = [
|
||||
Space::USERGROUP_OWNER,
|
||||
Space::USERGROUP_ADMIN,
|
||||
Space::USERGROUP_MODERATOR,
|
||||
];
|
||||
|
||||
protected $fixedGroups = [
|
||||
Space::USERGROUP_USER,
|
||||
Space::USERGROUP_GUEST,
|
||||
];
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Manage animals');
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return Yii::t('AnimalManagementModule.base', 'Allows creating, editing, and transferring animal records.');
|
||||
}
|
||||
}
|
||||
90
scripts/seed_horse_images.php
Normal file
90
scripts/seed_horse_images.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
$pdo = new PDO('mysql:host=humhub-db;dbname=humhub', 'humhub', 'Tiberi0u$', [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$animals = $pdo->query('SELECT id FROM rescue_animal ORDER BY id')->fetchAll(PDO::FETCH_COLUMN);
|
||||
if (!$animals) {
|
||||
echo "No animals found\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$coverId = (int)$pdo->query("SELECT id FROM rescue_field_definition WHERE module_id='animal_management' AND field_key='cover_image_url' LIMIT 1")->fetchColumn();
|
||||
$profileId = (int)$pdo->query("SELECT id FROM rescue_field_definition WHERE module_id='animal_management' AND field_key='profile_image_url' LIMIT 1")->fetchColumn();
|
||||
|
||||
$seedDir = '/var/www/localhost/htdocs/uploads/animal-management/seed';
|
||||
$seedFiles = glob($seedDir . '/*.jpg');
|
||||
sort($seedFiles);
|
||||
|
||||
if (empty($seedFiles)) {
|
||||
echo "No seed images downloaded\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$idx = 0;
|
||||
|
||||
$upsert = $pdo->prepare("INSERT INTO rescue_animal_field_value (animal_id, field_definition_id, value_text, value_json, created_at, updated_at) VALUES (:animal_id,:field_definition_id,:value_text,NULL,:created_at,:updated_at) ON DUPLICATE KEY UPDATE value_text=VALUES(value_text),updated_at=VALUES(updated_at)");
|
||||
$insertGallery = $pdo->prepare("INSERT INTO rescue_animal_gallery_item (animal_id, file_path, file_id, source_post_id, source_type, caption, created_by, created_at, updated_at) VALUES (:animal_id,:file_path,NULL,NULL,'seed',NULL,NULL,:created_at,:updated_at)");
|
||||
$deleteSeedGallery = $pdo->prepare("DELETE FROM rescue_animal_gallery_item WHERE animal_id=:animal_id AND source_type='seed'");
|
||||
|
||||
foreach ($animals as $animalIdRaw) {
|
||||
$animalId = (int)$animalIdRaw;
|
||||
$source = $seedFiles[$idx % count($seedFiles)];
|
||||
$idx++;
|
||||
|
||||
$targetDir = '/var/www/localhost/htdocs/uploads/animal-management/animals/' . $animalId;
|
||||
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir)) {
|
||||
echo "Could not create directory for animal {$animalId}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$stamp = time() . '-' . $animalId;
|
||||
$coverName = 'cover-seed-' . $stamp . '.jpg';
|
||||
$profileName = 'profile-seed-' . $stamp . '.jpg';
|
||||
$galleryName = 'gallery-seed-' . $stamp . '.jpg';
|
||||
|
||||
$coverPath = $targetDir . '/' . $coverName;
|
||||
$profilePath = $targetDir . '/' . $profileName;
|
||||
$galleryPath = $targetDir . '/' . $galleryName;
|
||||
|
||||
if (!copy($source, $coverPath) || !copy($source, $profilePath) || !copy($source, $galleryPath)) {
|
||||
echo "Could not copy seed image for animal {$animalId}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$coverUrl = '/uploads/animal-management/animals/' . $animalId . '/' . $coverName;
|
||||
$profileUrl = '/uploads/animal-management/animals/' . $animalId . '/' . $profileName;
|
||||
$galleryUrl = '/uploads/animal-management/animals/' . $animalId . '/' . $galleryName;
|
||||
|
||||
if ($coverId > 0) {
|
||||
$upsert->execute([
|
||||
':animal_id' => $animalId,
|
||||
':field_definition_id' => $coverId,
|
||||
':value_text' => $coverUrl,
|
||||
':created_at' => $now,
|
||||
':updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($profileId > 0) {
|
||||
$upsert->execute([
|
||||
':animal_id' => $animalId,
|
||||
':field_definition_id' => $profileId,
|
||||
':value_text' => $profileUrl,
|
||||
':created_at' => $now,
|
||||
':updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
$deleteSeedGallery->execute([':animal_id' => $animalId]);
|
||||
$insertGallery->execute([
|
||||
':animal_id' => $animalId,
|
||||
':file_path' => $galleryUrl,
|
||||
':created_at' => $now,
|
||||
':updated_at' => $now,
|
||||
]);
|
||||
|
||||
echo "Seeded animal {$animalId} with " . basename($source) . "\n";
|
||||
}
|
||||
87
services/AnimalStreamPublisherService.php
Normal file
87
services/AnimalStreamPublisherService.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\services;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\AnimalStreamEntry;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class AnimalStreamPublisherService
|
||||
{
|
||||
public static function publishMedicalVisit(Animal $animal, AnimalMedicalVisit $visit): void
|
||||
{
|
||||
if (!self::streamTableExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = AnimalStreamEntry::find()->where([
|
||||
'entry_type' => AnimalStreamEntry::TYPE_MEDICAL,
|
||||
'medical_visit_id' => (int)$visit->id,
|
||||
])->exists();
|
||||
|
||||
if ($exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::publishEntry($animal, AnimalStreamEntry::TYPE_MEDICAL, (int)$visit->id, null);
|
||||
}
|
||||
|
||||
public static function publishProgressUpdate(Animal $animal, AnimalProgressUpdate $update): void
|
||||
{
|
||||
if (!self::streamTableExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = AnimalStreamEntry::find()->where([
|
||||
'entry_type' => AnimalStreamEntry::TYPE_PROGRESS,
|
||||
'progress_update_id' => (int)$update->id,
|
||||
])->exists();
|
||||
|
||||
if ($exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::publishEntry($animal, AnimalStreamEntry::TYPE_PROGRESS, null, (int)$update->id);
|
||||
}
|
||||
|
||||
private static function publishEntry(Animal $animal, string $entryType, ?int $medicalVisitId, ?int $progressUpdateId): void
|
||||
{
|
||||
try {
|
||||
$space = Space::findOne(['contentcontainer_id' => (int)$animal->contentcontainer_id]);
|
||||
if (!$space instanceof Space) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entry = new AnimalStreamEntry();
|
||||
$entry->animal_id = (int)$animal->id;
|
||||
$entry->entry_type = $entryType;
|
||||
$entry->medical_visit_id = $medicalVisitId;
|
||||
$entry->progress_update_id = $progressUpdateId;
|
||||
$entry->content->container = $space;
|
||||
|
||||
if (!$entry->save()) {
|
||||
Yii::warning([
|
||||
'message' => 'Could not save animal stream entry.',
|
||||
'animal_id' => (int)$animal->id,
|
||||
'entry_type' => $entryType,
|
||||
'errors' => $entry->getErrors(),
|
||||
], 'animal_management.stream_publish');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Yii::warning([
|
||||
'message' => 'Unexpected error while publishing animal stream entry.',
|
||||
'animal_id' => (int)$animal->id,
|
||||
'entry_type' => $entryType,
|
||||
'exception' => $e->getMessage(),
|
||||
], 'animal_management.stream_publish');
|
||||
}
|
||||
}
|
||||
|
||||
private static function streamTableExists(): bool
|
||||
{
|
||||
return Yii::$app->db->schema->getTableSchema('rescue_animal_stream_entry', true) !== null;
|
||||
}
|
||||
}
|
||||
343
services/GalleryIntegrationService.php
Normal file
343
services/GalleryIntegrationService.php
Normal file
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\services;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryLink;
|
||||
use humhub\modules\gallery\models\CustomGallery;
|
||||
use humhub\modules\space\models\Space;
|
||||
use Yii;
|
||||
|
||||
class GalleryIntegrationService
|
||||
{
|
||||
public static function canSyncForSpace(Space $space): bool
|
||||
{
|
||||
if (!$space->moduleManager->isEnabled('animal_management')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$space->moduleManager->isEnabled('gallery')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::hasRequiredTables();
|
||||
}
|
||||
|
||||
public static function syncSpaceAnimalGalleries(Space $space): void
|
||||
{
|
||||
if (!self::canSyncForSpace($space)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$animals = Animal::find()->where(['contentcontainer_id' => (int)$space->contentcontainer_id])->all();
|
||||
$synced = 0;
|
||||
foreach ($animals as $animal) {
|
||||
if ($animal instanceof Animal) {
|
||||
if (self::ensureAnimalGallery($animal, $space) instanceof CustomGallery) {
|
||||
$synced++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Yii::warning([
|
||||
'space_id' => (int)$space->id,
|
||||
'contentcontainer_id' => (int)$space->contentcontainer_id,
|
||||
'animal_count' => count($animals),
|
||||
'synced_count' => $synced,
|
||||
'has_link_table' => self::hasLinkTable(),
|
||||
], 'animal_management.gallery_integration.sync');
|
||||
}
|
||||
|
||||
public static function ensureAnimalGallery(Animal $animal, ?Space $space = null): ?CustomGallery
|
||||
{
|
||||
if (!self::hasRequiredTables()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$space = $space ?: Space::findOne(['contentcontainer_id' => (int)$animal->contentcontainer_id]);
|
||||
if (!$space instanceof Space || !self::canSyncForSpace($space)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$link = self::hasLinkTable() ? AnimalGalleryLink::findOne(['animal_id' => (int)$animal->id]) : null;
|
||||
$gallery = null;
|
||||
|
||||
if ($link instanceof AnimalGalleryLink) {
|
||||
$gallery = CustomGallery::find()
|
||||
->contentContainer($space)
|
||||
->where(['gallery_gallery.id' => (int)$link->gallery_id])
|
||||
->one();
|
||||
}
|
||||
|
||||
if (!$gallery instanceof CustomGallery) {
|
||||
$gallery = CustomGallery::find()
|
||||
->contentContainer($space)
|
||||
->where(['like', 'description', self::buildMarker((int)$animal->id)])
|
||||
->one();
|
||||
|
||||
if (!$gallery instanceof CustomGallery) {
|
||||
$gallery = CustomGallery::find()
|
||||
->contentContainer($space)
|
||||
->where(['title' => self::buildTitle($animal)])
|
||||
->one();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gallery instanceof CustomGallery) {
|
||||
$gallery = new CustomGallery();
|
||||
$gallery->content->container = $space;
|
||||
$gallery->content->visibility = (int)$space->getDefaultContentVisibility();
|
||||
$gallery->title = self::buildTitle($animal);
|
||||
$gallery->description = self::buildDescription($animal);
|
||||
|
||||
try {
|
||||
if (!$gallery->save()) {
|
||||
Yii::warning($gallery->getErrors(), 'animal_management.gallery_integration.gallery_create_failed');
|
||||
return null;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Yii::warning($e->getMessage(), 'animal_management.gallery_integration.gallery_create_failed');
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$newTitle = self::buildTitle($animal);
|
||||
$newDescription = self::buildDescription($animal);
|
||||
$dirty = false;
|
||||
|
||||
if ((string)$gallery->title !== $newTitle) {
|
||||
$gallery->title = $newTitle;
|
||||
$dirty = true;
|
||||
}
|
||||
|
||||
if ((string)$gallery->description !== $newDescription) {
|
||||
$gallery->description = $newDescription;
|
||||
$dirty = true;
|
||||
}
|
||||
|
||||
if ($dirty) {
|
||||
try {
|
||||
$gallery->save();
|
||||
} catch (\Throwable $e) {
|
||||
Yii::warning($e->getMessage(), 'animal_management.gallery_integration.gallery_update_failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self::hasLinkTable()) {
|
||||
if (!$link instanceof AnimalGalleryLink) {
|
||||
$link = new AnimalGalleryLink();
|
||||
$link->animal_id = (int)$animal->id;
|
||||
}
|
||||
|
||||
$link->gallery_id = (int)$gallery->id;
|
||||
$link->contentcontainer_id = (int)$space->contentcontainer_id;
|
||||
try {
|
||||
if (!$link->save()) {
|
||||
Yii::warning($link->getErrors(), 'animal_management.gallery_integration.link_save_failed');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Yii::warning($e->getMessage(), 'animal_management.gallery_integration.link_save_failed');
|
||||
}
|
||||
}
|
||||
|
||||
self::refreshGalleryThumb($animal, $gallery);
|
||||
return $gallery;
|
||||
}
|
||||
|
||||
public static function isAnimalBackedGallery(CustomGallery $gallery): bool
|
||||
{
|
||||
return self::getAnimalByGalleryId((int)$gallery->id) instanceof Animal;
|
||||
}
|
||||
|
||||
public static function getAnimalByGalleryId(int $galleryId): ?Animal
|
||||
{
|
||||
if (!self::hasRequiredTables()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (self::hasLinkTable()) {
|
||||
$link = AnimalGalleryLink::findOne(['gallery_id' => $galleryId]);
|
||||
if ($link instanceof AnimalGalleryLink) {
|
||||
return Animal::findOne(['id' => (int)$link->animal_id]);
|
||||
}
|
||||
}
|
||||
|
||||
$gallery = CustomGallery::findOne(['id' => $galleryId]);
|
||||
if (!$gallery instanceof CustomGallery) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$animalId = self::extractAnimalIdFromDescription((string)$gallery->description);
|
||||
if ($animalId === null) {
|
||||
$animalId = self::extractAnimalIdFromTitle($gallery);
|
||||
if ($animalId === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return Animal::findOne(['id' => $animalId]);
|
||||
}
|
||||
|
||||
public static function getAnimalItemsQuery(CustomGallery $gallery)
|
||||
{
|
||||
$animal = self::getAnimalByGalleryId((int)$gallery->id);
|
||||
if (!$animal instanceof Animal) {
|
||||
Yii::warning([
|
||||
'gallery_id' => (int)$gallery->id,
|
||||
'title' => (string)$gallery->title,
|
||||
'description' => (string)$gallery->description,
|
||||
], 'animal_management.gallery_integration.gallery_unmapped');
|
||||
return null;
|
||||
}
|
||||
|
||||
return AnimalGalleryItem::find()
|
||||
->where(['animal_id' => (int)$animal->id])
|
||||
->andWhere([
|
||||
'or',
|
||||
['and', ['not', ['file_path' => null]], ['!=', 'file_path', '']],
|
||||
['not', ['file_id' => null]],
|
||||
])
|
||||
->orderBy(['id' => SORT_DESC]);
|
||||
}
|
||||
|
||||
public static function getAnimalPreviewUrl(CustomGallery $gallery): ?string
|
||||
{
|
||||
$animal = self::getAnimalByGalleryId((int)$gallery->id);
|
||||
if (!$animal instanceof Animal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = AnimalGalleryItem::find()
|
||||
->where(['animal_id' => (int)$animal->id])
|
||||
->orderBy(['id' => SORT_DESC])
|
||||
->one();
|
||||
|
||||
if (!$item instanceof AnimalGalleryItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = trim((string)$item->getImageUrl());
|
||||
return $url !== '' ? $url : null;
|
||||
}
|
||||
|
||||
public static function isAnimalGalleryEmpty(CustomGallery $gallery): ?bool
|
||||
{
|
||||
$animal = self::getAnimalByGalleryId((int)$gallery->id);
|
||||
if (!$animal instanceof Animal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->exists();
|
||||
}
|
||||
|
||||
private static function refreshGalleryThumb(Animal $animal, CustomGallery $gallery): void
|
||||
{
|
||||
$thumbFileId = AnimalGalleryItem::find()
|
||||
->where(['animal_id' => (int)$animal->id])
|
||||
->andWhere(['not', ['file_id' => null]])
|
||||
->orderBy(['id' => SORT_DESC])
|
||||
->select('file_id')
|
||||
->scalar();
|
||||
|
||||
$thumbFileId = $thumbFileId === null ? null : (int)$thumbFileId;
|
||||
if ((int)($gallery->thumb_file_id ?? 0) === (int)($thumbFileId ?? 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gallery->thumb_file_id = $thumbFileId;
|
||||
$gallery->save(false, ['thumb_file_id']);
|
||||
}
|
||||
|
||||
private static function hasRequiredTables(): bool
|
||||
{
|
||||
$schema = Yii::$app->db->schema;
|
||||
|
||||
return $schema->getTableSchema('rescue_animal', true) !== null
|
||||
&& $schema->getTableSchema('rescue_animal_gallery_item', true) !== null
|
||||
&& $schema->getTableSchema('gallery_gallery', true) !== null;
|
||||
}
|
||||
|
||||
private static function hasLinkTable(): bool
|
||||
{
|
||||
return Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_link', true) !== null;
|
||||
}
|
||||
|
||||
private static function buildTitle(Animal $animal): string
|
||||
{
|
||||
return $animal->getDisplayName() . ' Gallery';
|
||||
}
|
||||
|
||||
private static function buildDescription(Animal $animal): string
|
||||
{
|
||||
return self::buildMarker((int)$animal->id);
|
||||
}
|
||||
|
||||
private static function buildMarker(int $animalId): string
|
||||
{
|
||||
return '[animal-gallery:' . $animalId . ']';
|
||||
}
|
||||
|
||||
private static function extractAnimalIdFromDescription(string $description): ?int
|
||||
{
|
||||
if (!preg_match('/\[animal-gallery:(\d+)\]/', $description, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isset($matches[1]) ? (int)$matches[1] : null;
|
||||
}
|
||||
|
||||
private static function extractAnimalIdFromTitle(CustomGallery $gallery): ?int
|
||||
{
|
||||
$container = $gallery->content->container ?? null;
|
||||
if (!$container instanceof Space) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$title = trim((string)$gallery->title);
|
||||
if ($title === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$titleNormalized = self::normalizeTitleToken($title);
|
||||
if ($titleNormalized === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$animals = Animal::find()
|
||||
->where(['contentcontainer_id' => (int)$container->contentcontainer_id])
|
||||
->orderBy(['id' => SORT_ASC])
|
||||
->all();
|
||||
|
||||
foreach ($animals as $animal) {
|
||||
if (!$animal instanceof Animal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nameNormalized = self::normalizeTitleToken((string)$animal->name);
|
||||
if ($nameNormalized !== '' && ($nameNormalized === $titleNormalized || strpos($titleNormalized, $nameNormalized) !== false)) {
|
||||
return (int)$animal->id;
|
||||
}
|
||||
|
||||
$uidNormalized = self::normalizeTitleToken((string)$animal->animal_uid);
|
||||
if ($uidNormalized !== '' && ($uidNormalized === $titleNormalized || strpos($titleNormalized, $uidNormalized) !== false)) {
|
||||
return (int)$animal->id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function normalizeTitleToken(string $value): string
|
||||
{
|
||||
$value = strtolower(trim($value));
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = preg_replace('/\bgallery\b/i', ' ', $value);
|
||||
$value = preg_replace('/[^a-z0-9]+/i', ' ', (string)$value);
|
||||
return trim((string)$value);
|
||||
}
|
||||
|
||||
}
|
||||
93
views/animals/_tile.php
Normal file
93
views/animals/_tile.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Animal $animal */
|
||||
/* @var ContentContainerActiveRecord $contentContainer */
|
||||
/* @var AnimalMedicalVisit|null $lastMedical */
|
||||
/* @var string $imageUrl */
|
||||
/* @var array $tileFields */
|
||||
/* @var bool $showMedicalIcon */
|
||||
|
||||
$showMedicalIcon = isset($showMedicalIcon) ? (bool)$showMedicalIcon : true;
|
||||
$imageUrl = trim((string)$imageUrl);
|
||||
$hasImage = $imageUrl !== '' && (preg_match('/^https?:\/\//i', $imageUrl) || substr($imageUrl, 0, 1) === '/');
|
||||
|
||||
$statusLabel = Animal::statusOptions()[$animal->status] ?? (string)$animal->status;
|
||||
$fieldMap = [
|
||||
'name' => (string)$animal->getDisplayName(),
|
||||
'species' => (string)$animal->species,
|
||||
'breed' => (string)$animal->breed,
|
||||
'sex' => (string)$animal->sex,
|
||||
'status' => (string)$statusLabel,
|
||||
'location_name' => (string)$animal->location_name,
|
||||
'animal_uid' => (string)$animal->animal_uid,
|
||||
'public_summary' => trim((string)$animal->public_summary),
|
||||
'last_medical' => $lastMedical instanceof AnimalMedicalVisit ? DateDisplayHelper::format((string)$lastMedical->visit_at) : '',
|
||||
];
|
||||
|
||||
$tileFields = is_array($tileFields) ? $tileFields : [];
|
||||
$selectedMeta = [];
|
||||
foreach ($tileFields as $fieldKey) {
|
||||
$fieldKey = trim((string)$fieldKey);
|
||||
if ($fieldKey === '' || $fieldKey === 'name' || !array_key_exists($fieldKey, $fieldMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = trim((string)$fieldMap[$fieldKey]);
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedMeta[] = $value;
|
||||
}
|
||||
|
||||
$summaryText = trim((string)$fieldMap['public_summary']);
|
||||
if ($summaryText !== '') {
|
||||
$summaryText = substr($summaryText, 0, 160) . (strlen($summaryText) > 160 ? '...' : '');
|
||||
}
|
||||
|
||||
$animalViewUrl = $contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]);
|
||||
$medicalUrl = $contentContainer->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id]);
|
||||
?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:0;overflow:hidden;border-radius:12px;border:0;box-shadow:0 8px 24px rgba(15,23,42,0.14);">
|
||||
<div style="display:block;position:relative;aspect-ratio:4 / 3;background:#d8dee8;">
|
||||
<a href="<?= Html::encode($animalViewUrl) ?>" aria-label="<?= Html::encode($animal->getDisplayName()) ?>" style="position:absolute;inset:0;z-index:1;"></a>
|
||||
<?php if ($hasImage): ?>
|
||||
<img src="<?= Html::encode($imageUrl) ?>" alt="<?= Html::encode($animal->getDisplayName()) ?>" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;">
|
||||
<?php endif; ?>
|
||||
<div style="position:absolute;inset:0;background:linear-gradient(180deg, rgba(7,10,16,0.05) 0%, rgba(7,10,16,0.62) 58%, rgba(7,10,16,0.78) 100%);"></div>
|
||||
|
||||
<div style="position:absolute;left:12px;right:12px;bottom:12px;z-index:2;color:#fff;pointer-events:none;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;">
|
||||
<span style="font-size:20px;font-weight:700;line-height:1.15;text-shadow:0 2px 8px rgba(0,0,0,0.4);color:#fff;">
|
||||
<?= Html::encode($animal->getDisplayName()) ?>
|
||||
</span>
|
||||
<?php if ($showMedicalIcon): ?>
|
||||
<a href="<?= Html::encode($medicalUrl) ?>" class="btn btn-xs btn-default" title="<?= Yii::t('AnimalManagementModule.base', 'Medical Visits') ?>" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Medical Visits') ?>" style="border-radius:999px;padding:6px 8px;background:rgba(255,255,255,0.92);border:0;position:relative;z-index:3;pointer-events:auto;">
|
||||
<i class="fa fa-heartbeat"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($selectedMeta)): ?>
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px;">
|
||||
<?php foreach ($selectedMeta as $metaValue): ?>
|
||||
<span style="display:inline-block;background:rgba(15,23,42,0.58);border:1px solid rgba(255,255,255,0.28);padding:2px 8px;border-radius:999px;font-size:12px;">
|
||||
<?= Html::encode($metaValue) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($summaryText !== ''): ?>
|
||||
<div style="font-size:12px;line-height:1.35;color:rgba(255,255,255,0.92);text-shadow:0 1px 4px rgba(0,0,0,0.5);">
|
||||
<?= Html::encode($summaryText) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
130
views/animals/_transfer_tile.php
Normal file
130
views/animals/_transfer_tile.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\space\models\Space;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var AnimalTransfer $transfer */
|
||||
/* @var Space $space */
|
||||
/* @var string $otherRescueName */
|
||||
/* @var string $otherRescueUrl */
|
||||
/* @var string $animalProfileUrl */
|
||||
/* @var string $imageUrl */
|
||||
/* @var bool $isIncoming */
|
||||
|
||||
$animalName = $transfer->animal ? $transfer->animal->getDisplayName() : ('#' . (int)$transfer->animal_id);
|
||||
$statusLabel = AnimalTransfer::statusOptions()[$transfer->status] ?? (string)$transfer->status;
|
||||
$hasImage = $imageUrl !== '' && (preg_match('/^https?:\/\//i', $imageUrl) || substr($imageUrl, 0, 1) === '/');
|
||||
$statusTextColor = '#ffffff';
|
||||
switch ($transfer->status) {
|
||||
case AnimalTransfer::STATUS_REQUESTED:
|
||||
$statusTextColor = '#facc15';
|
||||
break;
|
||||
case AnimalTransfer::STATUS_ACCEPTED:
|
||||
$statusTextColor = '#4ade80';
|
||||
break;
|
||||
case AnimalTransfer::STATUS_COMPLETED:
|
||||
$statusTextColor = '#60a5fa';
|
||||
break;
|
||||
case AnimalTransfer::STATUS_DECLINED:
|
||||
$statusTextColor = '#fca5a5';
|
||||
break;
|
||||
case AnimalTransfer::STATUS_CANCELLED:
|
||||
$statusTextColor = '#d1d5db';
|
||||
break;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:0;overflow:hidden;border-radius:12px;border:0;box-shadow:0 8px 24px rgba(15,23,42,0.14);">
|
||||
<div style="position:relative;min-height:190px;background:#d8dee8;">
|
||||
<?php if ($hasImage): ?>
|
||||
<img src="<?= Html::encode($imageUrl) ?>" alt="<?= Html::encode($animalName) ?>" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#8d98a5;">
|
||||
<i class="fa fa-paw fa-3x"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="position:absolute;inset:0;background:linear-gradient(180deg, rgba(7,10,16,0.08) 0%, rgba(7,10,16,0.6) 58%, rgba(7,10,16,0.82) 100%);"></div>
|
||||
|
||||
<div style="position:absolute;top:12px;left:12px;z-index:2;max-width:60%;">
|
||||
<?php if (!empty($otherRescueUrl)): ?>
|
||||
<?= Html::a(
|
||||
Html::encode($otherRescueName),
|
||||
$otherRescueUrl,
|
||||
[
|
||||
'style' => 'display:inline-block;background:rgba(15,23,42,0.66);border:1px solid rgba(255,255,255,0.28);color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;text-decoration:none;'
|
||||
]
|
||||
) ?>
|
||||
<?php else: ?>
|
||||
<span style="display:inline-block;background:rgba(15,23,42,0.66);border:1px solid rgba(255,255,255,0.28);color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;">
|
||||
<?= Html::encode($otherRescueName) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="position:absolute;top:12px;right:12px;z-index:2;display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;max-width:65%;">
|
||||
<?php if ($isIncoming && $transfer->status === AnimalTransfer::STATUS_REQUESTED): ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'Accept'),
|
||||
$space->createUrl('/animal_management/animals/transfer-respond', ['id' => $transfer->id, 'decision' => 'accept']),
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-method' => 'post',
|
||||
'style' => 'border-radius:999px;border:0;background:rgba(255,255,255,0.92);font-weight:600;'
|
||||
]
|
||||
) ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'Decline'),
|
||||
$space->createUrl('/animal_management/animals/transfer-respond', ['id' => $transfer->id, 'decision' => 'decline']),
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-method' => 'post',
|
||||
'style' => 'border-radius:999px;border:0;background:rgba(255,255,255,0.92);font-weight:600;'
|
||||
]
|
||||
) ?>
|
||||
<?php elseif ($isIncoming && $transfer->status === AnimalTransfer::STATUS_ACCEPTED): ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'Complete Transfer'),
|
||||
$space->createUrl('/animal_management/animals/transfer-complete', ['id' => $transfer->id]),
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-method' => 'post',
|
||||
'style' => 'border-radius:999px;border:0;background:rgba(255,255,255,0.92);font-weight:600;'
|
||||
]
|
||||
) ?>
|
||||
<?php elseif (!$isIncoming && in_array($transfer->status, [AnimalTransfer::STATUS_REQUESTED, AnimalTransfer::STATUS_ACCEPTED], true)): ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'Cancel Request'),
|
||||
$space->createUrl('/animal_management/animals/transfer-cancel', ['id' => $transfer->id]),
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-method' => 'post',
|
||||
'style' => 'border-radius:999px;border:0;background:rgba(255,255,255,0.92);font-weight:600;'
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="position:absolute;left:12px;right:12px;bottom:12px;z-index:2;color:#fff;">
|
||||
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:10px;">
|
||||
<div style="font-size:24px;font-weight:700;line-height:1.15;text-shadow:0 2px 8px rgba(0,0,0,0.45);">
|
||||
<?php if (!empty($animalProfileUrl)): ?>
|
||||
<?= Html::a(
|
||||
Html::encode($animalName),
|
||||
$animalProfileUrl,
|
||||
[
|
||||
'style' => 'color:#fff;text-decoration:none;'
|
||||
]
|
||||
) ?>
|
||||
<?php else: ?>
|
||||
<?= Html::encode($animalName) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<span style="display:inline-block;background:rgba(15,23,42,0.7);border:1px solid rgba(255,255,255,0.22);padding:5px 12px;border-radius:999px;font-size:14px;font-weight:800;letter-spacing:0.02em;color:<?= Html::encode($statusTextColor) ?>;text-transform:uppercase;white-space:nowrap;">
|
||||
<?= Html::encode($statusLabel) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
518
views/animals/add-medical-visit.php
Normal file
518
views/animals/add-medical-visit.php
Normal file
@@ -0,0 +1,518 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\forms\AnimalMedicalVisitForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var AnimalMedicalVisitForm $model */
|
||||
/* @var string $returnTo */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
/* @var bool $isInline */
|
||||
|
||||
$isInline = isset($isInline) ? (bool)$isInline : false;
|
||||
|
||||
$hiddenMedicalKeys = [
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
];
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalMedicalVisitForm $formModel, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$vitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
$label = (string)($vitalLabelOverrides[$fieldKey] ?? $definition['label']);
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalMedicalVisitForm[customFields][$fieldKey]";
|
||||
$fieldValue = $formModel->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$formModel->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalmedicalvisitform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$customDefinitions = $model->getCustomFieldDefinitions();
|
||||
$knownMedicalKeys = [
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'medical_media_reference',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$remainingDefinitions = [];
|
||||
foreach ($customDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true) || in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$remainingDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$medicalMediaPath = trim((string)($model->customFields['medical_media_reference'] ?? $model->customFields['media_reference'] ?? ''));
|
||||
$hasMedicalMedia = $medicalMediaPath !== '' && (preg_match('/^https?:\/\//i', $medicalMediaPath) || substr($medicalMediaPath, 0, 1) === '/');
|
||||
$medicalGalleryModalId = 'add-medical-media-gallery-modal';
|
||||
$medicalFormId = 'add-medical-visit-inline-form';
|
||||
|
||||
$this->registerCss(<<<CSS
|
||||
.inline-add-shell.panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 12px;
|
||||
background: rgba(10, 18, 28, 0.36);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.inline-add-shell.panel.has-media::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(10, 18, 28, 0.22);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.inline-add-shell > .panel-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: rgba(10, 18, 28, 0.2);
|
||||
}
|
||||
|
||||
.inline-add-shell .panel.panel-default {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(10, 18, 28, 0.34);
|
||||
}
|
||||
|
||||
.inline-add-shell .panel.panel-default > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-add-shell,
|
||||
.inline-add-shell .panel-body,
|
||||
.inline-add-shell .control-label,
|
||||
.inline-add-shell .checkbox label,
|
||||
.inline-add-shell .radio label,
|
||||
.inline-add-shell .help-block {
|
||||
color: #eef5fb;
|
||||
}
|
||||
|
||||
.inline-add-shell .text-muted {
|
||||
color: rgba(233, 242, 250, 0.78) !important;
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control {
|
||||
background: rgba(10, 18, 28, 0.56);
|
||||
border-color: rgba(255, 255, 255, 0.44);
|
||||
color: #f3f8ff;
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control::placeholder {
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control[readonly],
|
||||
.inline-add-shell .form-control[disabled] {
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-add-shell select.form-control option {
|
||||
color: #0f1b2a;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
if ($isInline) {
|
||||
$this->registerCss(<<<CSS
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body > .panel:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.medical-media-select-thumb.is-selected {
|
||||
border-color: #1f8dd6;
|
||||
box-shadow: 0 0 0 2px rgba(31, 141, 214, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="panel panel-default inline-add-shell<?= $hasMedicalMedia ? ' has-media' : '' ?>" id="medical-inline-add-shell"<?= $hasMedicalMedia ? ' style="background-image:url(' . Html::encode($medicalMediaPath) . ');"' : '' ?>>
|
||||
<div class="panel-body">
|
||||
<?php
|
||||
$formOptions = ['id' => $medicalFormId, 'enctype' => 'multipart/form-data'];
|
||||
if (!$isInline) {
|
||||
$formOptions['target'] = '_top';
|
||||
}
|
||||
$form = ActiveForm::begin(['options' => $formOptions]);
|
||||
?>
|
||||
<?= Html::hiddenInput('returnTo', (string)($returnTo ?? 'medical-visits')) ?>
|
||||
<?= Html::hiddenInput('medicalMediaGalleryPath', $medicalMediaPath, ['id' => 'medical-media-gallery-path']) ?>
|
||||
|
||||
<?php if ($isInline): ?>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:10px;">
|
||||
<?= Html::submitButton('<i class="fa fa-check"></i>', [
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Save Medical Visit'),
|
||||
'form' => $medicalFormId,
|
||||
]) ?>
|
||||
<?= Html::button('<i class="fa fa-times"></i>', [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'id' => 'medical-inline-add-cancel-icon',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="medical-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($hasMedicalMedia): ?>
|
||||
<img src="<?= Html::encode($medicalMediaPath) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected medical media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#<?= Html::encode($medicalGalleryModalId) ?>" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeMedicalMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $form->field($model, 'visit_at')->input('datetime-local') ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'provider_name') ?></div>
|
||||
</div>
|
||||
<?= $form->field($model, 'notes')->textarea(['rows' => 3]) ?>
|
||||
<?= $form->field($model, 'recommendations')->textarea(['rows' => 3]) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><?= $renderCustomField('weight', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('pulse', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('blood_pressure', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('oxygen', $model, $customDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Conditions') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('chronic_conditions', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('acute_conditions', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('special_needs', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit Detail') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('date_of_most_recent_medical_visit', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Physician') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_business_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('physician_street_address', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_city', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_state', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_zip', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_cell_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_business_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_license_number', $model, $customDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Social Post') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $form->field($model, 'post_to_space_feed')->checkbox() ?>
|
||||
<?= $form->field($model, 'post_to_animal_feed')->checkbox() ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($remainingDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($remainingDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $model, $remainingDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Medical Visit'))->submit() ?>
|
||||
<?php if ($isInline): ?>
|
||||
<?= Html::button(Yii::t('AnimalManagementModule.base', 'Cancel'), [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default',
|
||||
'id' => 'medical-inline-add-cancel',
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
|
||||
->link(($returnTo ?? 'medical-visits') === 'medical-visits'
|
||||
? $space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])
|
||||
: $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="<?= Html::encode($medicalGalleryModalId) ?>" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Medical Media from Gallery') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default medical-media-select-thumb<?= $medicalMediaPath === $galleryUrl ? ' is-selected' : '' ?>" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;display:block;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="medicalMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="medicalMediaUpload" name="medicalMediaUpload" form="<?= Html::encode($medicalFormId) ?>" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerJs(<<<JS
|
||||
(function(){
|
||||
function escapeCssUrl(source) {
|
||||
return String(source || '').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
function setMedicalShellBackground(source) {
|
||||
var shell = $('#medical-inline-add-shell');
|
||||
if (!shell.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
shell.addClass('has-media').css('background-image', 'url("' + escapeCssUrl(source) + '")');
|
||||
} else {
|
||||
shell.removeClass('has-media').css('background-image', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
function renderMedicalPreview(source) {
|
||||
var preview = $('#medical-media-preview');
|
||||
if (!preview.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
preview.html('<img src="' + source + '" alt="Selected medical media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
} else {
|
||||
preview.html('<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>');
|
||||
}
|
||||
|
||||
setMedicalShellBackground(source);
|
||||
}
|
||||
|
||||
function markSelectedMedicalThumb(value) {
|
||||
$('.medical-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.medical-media-select-thumb').each(function() {
|
||||
if ($(this).data('media-url') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', '.medical-media-select-thumb', function() {
|
||||
var mediaUrl = $(this).data('media-url');
|
||||
$('#medical-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMedicalThumb(mediaUrl);
|
||||
$('#medicalMediaUpload').val('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
renderMedicalPreview(mediaUrl);
|
||||
}
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
});
|
||||
|
||||
$('#medicalMediaUpload').on('change', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#medical-media-gallery-path').val('');
|
||||
markSelectedMedicalThumb('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
renderMedicalPreview(e.target.result);
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#{$medicalGalleryModalId}').on('shown.bs.modal', function() {
|
||||
markSelectedMedicalThumb($('#medical-media-gallery-path').val());
|
||||
});
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
|
||||
if ($isInline) {
|
||||
$cancelPayload = Json::htmlEncode([
|
||||
'source' => 'animal-inline-editor',
|
||||
'type' => 'cancel',
|
||||
'collapseId' => 'medical-add-inline',
|
||||
]);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
$(document).on('click', '#medical-inline-add-cancel, #medical-inline-add-cancel-icon', function() {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage($cancelPayload, '*');
|
||||
}
|
||||
});
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
}
|
||||
?>
|
||||
445
views/animals/add-progress-update.php
Normal file
445
views/animals/add-progress-update.php
Normal file
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\forms\AnimalProgressUpdateForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var AnimalProgressUpdateForm $model */
|
||||
/* @var string $returnTo */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
/* @var bool $isInline */
|
||||
|
||||
$isInline = isset($isInline) ? (bool)$isInline : false;
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalProgressUpdateForm $formModel, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$label = (string)$definition['label'];
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalProgressUpdateForm[customFields][$fieldKey]";
|
||||
$fieldValue = $formModel->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$formModel->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalprogressupdateform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$customDefinitions = $model->getCustomFieldDefinitions();
|
||||
$knownProgressKeys = ['progress_notes', 'routine_updates', 'media_reference'];
|
||||
$otherCustomDefinitions = [];
|
||||
foreach ($customDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownProgressKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$otherCustomDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$currentMediaReference = trim((string)($model->customFields['media_reference'] ?? ''));
|
||||
$progressFormId = 'add-progress-update-inline-form';
|
||||
|
||||
$this->registerCss(<<<CSS
|
||||
.inline-add-shell.panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 12px;
|
||||
background: rgba(10, 18, 28, 0.36);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.inline-add-shell.panel.has-media::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(10, 18, 28, 0.22);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.inline-add-shell > .panel-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: rgba(10, 18, 28, 0.2);
|
||||
}
|
||||
|
||||
.inline-add-shell .panel.panel-default {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(10, 18, 28, 0.34);
|
||||
}
|
||||
|
||||
.inline-add-shell .panel.panel-default > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-add-shell,
|
||||
.inline-add-shell .panel-body,
|
||||
.inline-add-shell .control-label,
|
||||
.inline-add-shell .checkbox label,
|
||||
.inline-add-shell .radio label,
|
||||
.inline-add-shell .help-block {
|
||||
color: #eef5fb;
|
||||
}
|
||||
|
||||
.inline-add-shell .text-muted {
|
||||
color: rgba(233, 242, 250, 0.78) !important;
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control {
|
||||
background: rgba(10, 18, 28, 0.56);
|
||||
border-color: rgba(255, 255, 255, 0.44);
|
||||
color: #f3f8ff;
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control::placeholder {
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-add-shell .form-control[readonly],
|
||||
.inline-add-shell .form-control[disabled] {
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-add-shell select.form-control option {
|
||||
color: #0f1b2a;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
if ($isInline) {
|
||||
$this->registerCss(<<<CSS
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body > .panel:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="panel panel-default inline-add-shell<?= $currentMediaReference !== '' ? ' has-media' : '' ?>" id="progress-inline-add-shell"<?= $currentMediaReference !== '' ? ' style="background-image:url(' . Html::encode($currentMediaReference) . ');"' : '' ?>>
|
||||
<div class="panel-body">
|
||||
<?php
|
||||
$formOptions = ['id' => $progressFormId, 'enctype' => 'multipart/form-data'];
|
||||
if (!$isInline) {
|
||||
$formOptions['target'] = '_top';
|
||||
}
|
||||
$form = ActiveForm::begin(['options' => $formOptions]);
|
||||
?>
|
||||
<?= Html::hiddenInput('returnTo', (string)($returnTo ?? 'progress-updates')) ?>
|
||||
|
||||
<?php if ($isInline): ?>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:10px;">
|
||||
<?= Html::submitButton('<i class="fa fa-check"></i>', [
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Save Progress Update'),
|
||||
'form' => $progressFormId,
|
||||
]) ?>
|
||||
<?= Html::button('<i class="fa fa-times"></i>', [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'id' => 'progress-inline-add-cancel-icon',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<input type="hidden" id="progress-media-gallery-path" name="progressMediaGalleryPath" value="<?= Html::encode($currentMediaReference) ?>">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="progress-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($currentMediaReference !== '' && (preg_match('/^https?:\/\//i', $currentMediaReference) || substr($currentMediaReference, 0, 1) === '/')): ?>
|
||||
<img src="<?= Html::encode($currentMediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#progress-media-modal" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeProgressMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Progress Update') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><?= $form->field($model, 'weight') ?></div>
|
||||
<div class="col-sm-8"><?= $form->field($model, 'vitals')->textInput(['maxlength' => 255]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'behavior_notes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'medical_concerns')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'meal_plan_changes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'housing_changes')->textarea(['rows' => 2]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Notes') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('progress_notes', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('routine_updates', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($otherCustomDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($otherCustomDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $model, $otherCustomDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Social Post') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $form->field($model, 'post_to_space_feed')->checkbox() ?>
|
||||
<?= $form->field($model, 'post_to_animal_feed')->checkbox() ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="progress-media-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Progress Media') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default progress-media-select-thumb<?= $currentMediaReference === $galleryUrl ? ' is-selected' : '' ?>" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;border-radius:4px;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="progressMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="progressMediaUpload" name="progressMediaUpload" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Progress Update'))->submit() ?>
|
||||
<?php if ($isInline): ?>
|
||||
<?= Html::button(Yii::t('AnimalManagementModule.base', 'Cancel'), [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default',
|
||||
'id' => 'progress-inline-add-cancel',
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
|
||||
->link(($returnTo ?? 'progress-updates') === 'progress-updates'
|
||||
? $space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])
|
||||
: $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerCss(<<<CSS
|
||||
.progress-media-select-thumb.is-selected {
|
||||
border-color: #2f7df4 !important;
|
||||
box-shadow: 0 0 0 2px rgba(47, 125, 244, 0.22);
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
(function() {
|
||||
function escapeCssUrl(source) {
|
||||
return String(source || '').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
function setProgressShellBackground(source) {
|
||||
var shell = $('#progress-inline-add-shell');
|
||||
if (!shell.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
shell.addClass('has-media').css('background-image', 'url("' + escapeCssUrl(source) + '")');
|
||||
} else {
|
||||
shell.removeClass('has-media').css('background-image', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
function renderProgressPreview(source) {
|
||||
var preview = $('#progress-media-preview');
|
||||
if (!preview.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
preview.html('<img src="' + source + '" alt="Selected media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
} else {
|
||||
preview.html('<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>');
|
||||
}
|
||||
|
||||
setProgressShellBackground(source);
|
||||
}
|
||||
|
||||
function markSelectedMediaThumb(value) {
|
||||
$('.progress-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.progress-media-select-thumb').each(function() {
|
||||
if (($(this).attr('data-media-url') || '') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).off('click.addProgressMediaSelect', '.progress-media-select-thumb').on('click.addProgressMediaSelect', '.progress-media-select-thumb', function() {
|
||||
var mediaUrl = $(this).attr('data-media-url') || '';
|
||||
$('#progress-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMediaThumb(mediaUrl);
|
||||
$('#progressMediaUpload').val('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
renderProgressPreview(mediaUrl);
|
||||
}
|
||||
$('#progress-media-modal').modal('hide');
|
||||
});
|
||||
|
||||
$(document).off('change.addProgressMediaUpload', '#progressMediaUpload').on('change.addProgressMediaUpload', '#progressMediaUpload', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#progress-media-gallery-path').val('');
|
||||
markSelectedMediaThumb('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
renderProgressPreview(e.target.result);
|
||||
$('#progress-media-modal').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$(document).off('shown.bs.modal.addProgressMediaModal', '#progress-media-modal').on('shown.bs.modal.addProgressMediaModal', '#progress-media-modal', function() {
|
||||
markSelectedMediaThumb($('#progress-media-gallery-path').val());
|
||||
});
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
|
||||
if ($isInline) {
|
||||
$cancelPayload = Json::htmlEncode([
|
||||
'source' => 'animal-inline-editor',
|
||||
'type' => 'cancel',
|
||||
'collapseId' => 'progress-add-inline',
|
||||
]);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
$(document).on('click', '#progress-inline-add-cancel, #progress-inline-add-cancel-icon', function() {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage($cancelPayload, '*');
|
||||
}
|
||||
});
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
}
|
||||
?>
|
||||
534
views/animals/create.php
Normal file
534
views/animals/create.php
Normal file
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\forms\AnimalForm;
|
||||
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\rescue_foundation\components\UploadStandards;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var AnimalForm $model */
|
||||
/* @var Space $space */
|
||||
/* @var bool $isEdit */
|
||||
/* @var Animal|null $animal */
|
||||
?>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<?php if (!empty($isEdit)): ?>
|
||||
<?= Yii::t('AnimalManagementModule.base', '<strong>Edit Animal</strong> Profile') ?>
|
||||
<?php else: ?>
|
||||
<?= Yii::t('AnimalManagementModule.base', '<strong>New Animal</strong> Intake') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ?>
|
||||
|
||||
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
|
||||
|
||||
<?php
|
||||
$customDefinitions = $model->getCustomFieldDefinitions();
|
||||
$galleryImageOptions = $model->getGalleryImageOptions();
|
||||
$galleryImageUrls = array_keys($galleryImageOptions);
|
||||
|
||||
$knownProfileFieldKeys = [
|
||||
'dob',
|
||||
'age',
|
||||
'rescue',
|
||||
'lineage',
|
||||
'backstory',
|
||||
'previous_owner_user_id',
|
||||
'previous_owner_name',
|
||||
'previous_owner_business_name',
|
||||
'previous_owner_street_address',
|
||||
'previous_owner_city',
|
||||
'previous_owner_state',
|
||||
'previous_owner_zip',
|
||||
'previous_owner_cell_phone',
|
||||
'previous_owner_business_phone',
|
||||
'previous_owner_email',
|
||||
];
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalForm $model, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$label = (string)$definition['label'];
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalForm[customFields][$fieldKey]";
|
||||
$fieldValue = $model->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$model->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Profile & Cover Images') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="help-block" style="margin-top:0;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Tap the edit icon on either image to choose from gallery thumbnails or upload from device camera roll.') ?>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6" style="margin-bottom:10px;">
|
||||
<div id="animal-cover-preview" style="position:relative;border-radius:10px;overflow:hidden;background:#eef1f4;min-height:180px;">
|
||||
<?php if ($model->getExistingCoverImagePath()): ?>
|
||||
<img src="<?= Html::encode($model->getExistingCoverImagePath()) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Cover Image') ?>" style="width:100%;height:220px;object-fit:cover;display:block;">
|
||||
<?php else: ?>
|
||||
<div style="height:220px;display:flex;align-items:center;justify-content:center;color:#9ba5af;">
|
||||
<i class="fa fa-image fa-3x"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#animal-cover-image-manage-modal" style="position:absolute;top:10px;right:10px;border-radius:999px;">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div style="margin-top:6px;font-weight:600;"><?= Yii::t('AnimalManagementModule.base', 'Cover Image') ?></div>
|
||||
</div>
|
||||
<div class="col-sm-6" style="margin-bottom:10px;">
|
||||
<div id="animal-profile-preview" style="position:relative;border-radius:10px;overflow:hidden;background:#eef1f4;min-height:180px;">
|
||||
<?php if ($model->getExistingProfileImagePath()): ?>
|
||||
<img src="<?= Html::encode($model->getExistingProfileImagePath()) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Profile Image') ?>" style="width:100%;height:220px;object-fit:cover;display:block;">
|
||||
<?php else: ?>
|
||||
<div style="height:220px;display:flex;align-items:center;justify-content:center;color:#9ba5af;">
|
||||
<i class="fa fa-user-circle fa-3x"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#animal-profile-image-manage-modal" style="position:absolute;top:10px;right:10px;border-radius:999px;">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div style="margin-top:6px;font-weight:600;"><?= Yii::t('AnimalManagementModule.base', 'Profile Image') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:none;">
|
||||
<?= $form->field($model, 'coverImageGalleryPath')->hiddenInput()->label(false) ?>
|
||||
<?= $form->field($model, 'profileImageGalleryPath')->hiddenInput()->label(false) ?>
|
||||
<?= $form->field($model, 'coverImageFile')->fileInput(['accept' => 'image/*']) ?>
|
||||
<?= $form->field($model, 'profileImageFile')->fileInput(['accept' => 'image/*']) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('name')) { echo $form->field($model, 'name')->textInput(['maxlength' => 190]); } ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('species')) { echo $form->field($model, 'species')->textInput(['maxlength' => 120]); } ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('breed')) { echo $form->field($model, 'breed')->textInput(['maxlength' => 120]); } ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('sex')) { echo $form->field($model, 'sex')->textInput(['maxlength' => 32]); } ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('dob', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('age', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('status')) { echo $form->field($model, 'status')->dropDownList($model->getStatusOptions()); } ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('in_possession')) { echo $form->field($model, 'in_possession')->checkbox(); } ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Location') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $renderCustomField('rescue', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?php if ($model->isFieldActive('location_name')) { echo $form->field($model, 'location_name')->textInput(['maxlength' => 120]); } ?></div>
|
||||
<div class="col-sm-4"><?php if ($model->isFieldActive('city')) { echo $form->field($model, 'city')->textInput(['maxlength' => 120]); } ?></div>
|
||||
<div class="col-sm-4"><?php if ($model->isFieldActive('state')) { echo $form->field($model, 'state')->textInput(['maxlength' => 2]); } ?></div>
|
||||
<div class="col-sm-4"><?php if ($model->isFieldActive('zip')) { echo $form->field($model, 'zip')->textInput(['maxlength' => 10]); } ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'History') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('lineage', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('backstory', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Public') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php if ($model->isFieldActive('public_summary')): ?>
|
||||
<?= $form->field($model, 'public_summary')->textarea(['rows' => 4]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Previous Owner') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $renderCustomField('previous_owner_user_id', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('previous_owner_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('previous_owner_business_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('previous_owner_street_address', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('previous_owner_city', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('previous_owner_state', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('previous_owner_zip', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('previous_owner_cell_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('previous_owner_business_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('previous_owner_email', $model, $customDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Display Field Overrides') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<label class="control-label" style="display:block;"><?= Yii::t('AnimalManagementModule.base', 'Tile Fields') ?></label>
|
||||
<?= Html::checkboxList('AnimalForm[tileDisplayFields]', $model->tileDisplayFields, DisplaySettingsForm::fieldOptions(), ['separator' => '<br>']) ?>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="control-label" style="display:block;"><?= Yii::t('AnimalManagementModule.base', 'Hero Fields') ?></label>
|
||||
<?= Html::checkboxList('AnimalForm[heroDisplayFields]', $model->heroDisplayFields, DisplaySettingsForm::fieldOptions(), ['separator' => '<br>']) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$remainingCustomFields = [];
|
||||
foreach ($customDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownProfileFieldKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$remainingCustomFields[$fieldKey] = $definition;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (!empty($remainingCustomFields)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:14px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Custom Fields') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($remainingCustomFields as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $model, $remainingCustomFields) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Button::save(!empty($isEdit)
|
||||
? Yii::t('AnimalManagementModule.base', 'Save Changes')
|
||||
: Yii::t('AnimalManagementModule.base', 'Create Animal'))->submit() ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))->link(
|
||||
!empty($isEdit) && $animal instanceof Animal
|
||||
? $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])
|
||||
: $space->createUrl('/animal_management/animals/index')
|
||||
) ?>
|
||||
<?php if (!empty($isEdit) && $animal instanceof Animal): ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'Delete Animal'),
|
||||
$space->createUrl('/animal_management/animals/delete', ['id' => $animal->id]),
|
||||
[
|
||||
'class' => 'btn btn-danger pull-right',
|
||||
'style' => 'margin-left:8px;',
|
||||
'data-method' => 'post',
|
||||
'data-confirm' => Yii::t('AnimalManagementModule.base', 'Delete {name}? This will remove the animal record and local gallery uploads. This cannot be undone.', ['name' => $animal->getDisplayName()]),
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="modal fade" id="animal-cover-image-manage-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Manage Cover Image') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="help-block"><?= Yii::t('AnimalManagementModule.base', 'Select an existing gallery image or upload a new cover image.') ?></div>
|
||||
<div class="alert alert-info" style="padding:8px 10px;margin-bottom:10px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Your image preview updates immediately. Final save happens when you click {action} at the bottom of this intake form.', [
|
||||
'action' => !empty($isEdit)
|
||||
? Yii::t('AnimalManagementModule.base', 'Save Changes')
|
||||
: Yii::t('AnimalManagementModule.base', 'Create Animal'),
|
||||
]) ?>
|
||||
</div>
|
||||
<?php if (!empty($galleryImageUrls)): ?>
|
||||
<div class="row" style="max-height:260px;overflow:auto;margin-bottom:8px;">
|
||||
<?php foreach ($galleryImageUrls as $galleryUrl): ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default animal-image-select-thumb" data-select-kind="cover" data-select-target="#animalform-coverimagegallerypath" data-select-value="<?= Html::encode($galleryUrl) ?>" style="padding:3px;width:100%;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Cover option') ?>" style="width:100%;height:120px;object-fit:cover;border-radius:4px;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="text-muted" style="margin-bottom:8px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-default" id="animal-cover-upload-trigger">
|
||||
<i class="fa fa-upload"></i> <?= Yii::t('AnimalManagementModule.base', 'Upload New Cover Image') ?>
|
||||
</button>
|
||||
<div id="animal-cover-upload-file-name" class="help-block" style="margin-bottom:0;"></div>
|
||||
</div>
|
||||
<?= $form->field($model, 'removeCoverImage')->checkbox() ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><?= Yii::t('AnimalManagementModule.base', 'Done') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="animal-profile-image-manage-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Manage Profile Image') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="help-block"><?= Yii::t('AnimalManagementModule.base', 'Select an existing gallery image or upload a new profile image.') ?></div>
|
||||
<div class="alert alert-info" style="padding:8px 10px;margin-bottom:10px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Your image preview updates immediately. Final save happens when you click {action} at the bottom of this intake form.', [
|
||||
'action' => !empty($isEdit)
|
||||
? Yii::t('AnimalManagementModule.base', 'Save Changes')
|
||||
: Yii::t('AnimalManagementModule.base', 'Create Animal'),
|
||||
]) ?>
|
||||
</div>
|
||||
<?php if (!empty($galleryImageUrls)): ?>
|
||||
<div class="row" style="max-height:260px;overflow:auto;margin-bottom:8px;">
|
||||
<?php foreach ($galleryImageUrls as $galleryUrl): ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default animal-image-select-thumb" data-select-kind="profile" data-select-target="#animalform-profileimagegallerypath" data-select-value="<?= Html::encode($galleryUrl) ?>" style="padding:3px;width:100%;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Profile option') ?>" style="width:100%;height:120px;object-fit:cover;border-radius:4px;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="text-muted" style="margin-bottom:8px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-default" id="animal-profile-upload-trigger">
|
||||
<i class="fa fa-upload"></i> <?= Yii::t('AnimalManagementModule.base', 'Upload New Profile Image') ?>
|
||||
</button>
|
||||
<div id="animal-profile-upload-file-name" class="help-block" style="margin-bottom:0;"></div>
|
||||
</div>
|
||||
<?= $form->field($model, 'removeProfileImage')->checkbox() ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><?= Yii::t('AnimalManagementModule.base', 'Done') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerCss(<<<CSS
|
||||
.animal-image-select-thumb.is-selected {
|
||||
border-color: #2f7df4 !important;
|
||||
box-shadow: 0 0 0 2px rgba(47, 125, 244, 0.22);
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
(function() {
|
||||
function renderPlaceholder(kind) {
|
||||
if (kind === 'profile') {
|
||||
return '<div style="height:220px;display:flex;align-items:center;justify-content:center;color:#9ba5af;"><i class="fa fa-user-circle fa-3x"></i></div>';
|
||||
}
|
||||
|
||||
return '<div style="height:220px;display:flex;align-items:center;justify-content:center;color:#9ba5af;"><i class="fa fa-image fa-3x"></i></div>';
|
||||
}
|
||||
|
||||
function updatePreview(kind, value) {
|
||||
var previewId = kind === 'profile' ? '#animal-profile-preview' : '#animal-cover-preview';
|
||||
if (value && (value.indexOf('/') === 0 || /^https?:\/\//i.test(value) || /^data:image\//i.test(value))) {
|
||||
$(previewId).html('<img src="' + value + '" alt="' + kind + ' image" style="width:100%;height:220px;object-fit:cover;display:block;">' +
|
||||
'<button type="button" class="btn btn-default" data-toggle="modal" data-target="' + (kind === 'profile' ? '#animal-profile-image-manage-modal' : '#animal-cover-image-manage-modal') + '" style="position:absolute;top:10px;right:10px;border-radius:999px;"><i class="fa fa-pencil"></i></button>');
|
||||
return;
|
||||
}
|
||||
|
||||
$(previewId).html(renderPlaceholder(kind) +
|
||||
'<button type="button" class="btn btn-default" data-toggle="modal" data-target="' + (kind === 'profile' ? '#animal-profile-image-manage-modal' : '#animal-cover-image-manage-modal') + '" style="position:absolute;top:10px;right:10px;border-radius:999px;"><i class="fa fa-pencil"></i></button>');
|
||||
}
|
||||
|
||||
function markSelectedThumb(kind, value) {
|
||||
var modalId = kind === 'profile' ? '#animal-profile-image-manage-modal' : '#animal-cover-image-manage-modal';
|
||||
$(modalId + ' .animal-image-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(modalId + ' .animal-image-select-thumb').each(function() {
|
||||
if ($(this).data('select-value') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', '.animal-image-select-thumb', function() {
|
||||
var kind = $(this).data('select-kind') || 'cover';
|
||||
var target = $(this).data('select-target');
|
||||
var value = $(this).data('select-value');
|
||||
if (target && value !== undefined) {
|
||||
$(target).val(value);
|
||||
if (kind === 'profile') {
|
||||
$('#animalform-profileimagefile').val('');
|
||||
$('#animalform-removeprofileimage').prop('checked', false);
|
||||
} else {
|
||||
$('#animalform-coverimagefile').val('');
|
||||
$('#animalform-removecoverimage').prop('checked', false);
|
||||
}
|
||||
updatePreview(kind, value);
|
||||
markSelectedThumb(kind, value);
|
||||
if (kind === 'profile') {
|
||||
$('#animal-profile-upload-file-name').text('');
|
||||
} else {
|
||||
$('#animal-cover-upload-file-name').text('');
|
||||
}
|
||||
}
|
||||
|
||||
$(this).closest('.modal').modal('hide');
|
||||
});
|
||||
|
||||
$('#animal-cover-upload-trigger').on('click', function() {
|
||||
$('#animalform-coverimagefile').trigger('click');
|
||||
});
|
||||
|
||||
$('#animal-profile-upload-trigger').on('click', function() {
|
||||
$('#animalform-profileimagefile').trigger('click');
|
||||
});
|
||||
|
||||
$('#animalform-coverimagefile').on('change', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
$('#animalform-coverimagegallerypath').val('');
|
||||
$('#animalform-removecoverimage').prop('checked', false);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
updatePreview('cover', e.target.result);
|
||||
markSelectedThumb('cover', '');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
$('#animal-cover-upload-file-name').text(file.name || '');
|
||||
$('#animal-cover-image-manage-modal').modal('hide');
|
||||
});
|
||||
|
||||
$('#animalform-profileimagefile').on('change', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
$('#animalform-profileimagegallerypath').val('');
|
||||
$('#animalform-removeprofileimage').prop('checked', false);
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
updatePreview('profile', e.target.result);
|
||||
markSelectedThumb('profile', '');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
$('#animal-profile-upload-file-name').text(file.name || '');
|
||||
$('#animal-profile-image-manage-modal').modal('hide');
|
||||
});
|
||||
|
||||
$('#animalform-removecoverimage').on('change', function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#animalform-coverimagegallerypath').val('');
|
||||
$('#animalform-coverimagefile').val('');
|
||||
$('#animal-cover-upload-file-name').text('');
|
||||
markSelectedThumb('cover', '');
|
||||
updatePreview('cover', '');
|
||||
}
|
||||
});
|
||||
|
||||
$('#animalform-removeprofileimage').on('change', function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#animalform-profileimagegallerypath').val('');
|
||||
$('#animalform-profileimagefile').val('');
|
||||
$('#animal-profile-upload-file-name').text('');
|
||||
markSelectedThumb('profile', '');
|
||||
updatePreview('profile', '');
|
||||
}
|
||||
});
|
||||
|
||||
$('#animal-cover-image-manage-modal').on('shown.bs.modal', function() {
|
||||
markSelectedThumb('cover', $('#animalform-coverimagegallerypath').val());
|
||||
});
|
||||
|
||||
$('#animal-profile-image-manage-modal').on('shown.bs.modal', function() {
|
||||
markSelectedThumb('profile', $('#animalform-profileimagegallerypath').val());
|
||||
});
|
||||
})();
|
||||
JS
|
||||
);
|
||||
?>
|
||||
497
views/animals/edit-medical-visit.php
Normal file
497
views/animals/edit-medical-visit.php
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\forms\AnimalMedicalVisitForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var AnimalMedicalVisitForm $model */
|
||||
/* @var AnimalMedicalVisit $medicalVisit */
|
||||
/* @var string $returnTo */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
/* @var bool $isInline */
|
||||
|
||||
$isInline = isset($isInline) ? (bool)$isInline : false;
|
||||
|
||||
$hiddenMedicalKeys = [
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
];
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalMedicalVisitForm $formModel, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$vitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
$label = (string)($vitalLabelOverrides[$fieldKey] ?? $definition['label']);
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalMedicalVisitForm[customFields][$fieldKey]";
|
||||
$fieldValue = $formModel->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$formModel->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalmedicalvisitform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$customDefinitions = $model->getCustomFieldDefinitions();
|
||||
$knownMedicalKeys = [
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'medical_media_reference',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$remainingDefinitions = [];
|
||||
foreach ($customDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true) || in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$remainingDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$medicalMediaPath = trim((string)($model->customFields['medical_media_reference'] ?? $model->customFields['media_reference'] ?? ''));
|
||||
$hasMedicalMedia = $medicalMediaPath !== '' && (preg_match('/^https?:\/\//i', $medicalMediaPath) || substr($medicalMediaPath, 0, 1) === '/');
|
||||
$medicalGalleryModalId = 'edit-medical-media-gallery-modal';
|
||||
$medicalFormId = 'edit-medical-visit-form';
|
||||
|
||||
$this->registerCss(<<<CSS
|
||||
.inline-editor-shell.panel {
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 12px;
|
||||
background: rgba(10, 18, 28, 0.36);
|
||||
}
|
||||
|
||||
.inline-editor-shell > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell > .panel-body {
|
||||
background: rgba(10, 18, 28, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell .panel.panel-default {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(10, 18, 28, 0.34);
|
||||
}
|
||||
|
||||
.inline-editor-shell .panel.panel-default > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell,
|
||||
.inline-editor-shell .panel-body,
|
||||
.inline-editor-shell .control-label,
|
||||
.inline-editor-shell .checkbox label,
|
||||
.inline-editor-shell .radio label,
|
||||
.inline-editor-shell .help-block {
|
||||
color: #eef5fb;
|
||||
}
|
||||
|
||||
.inline-editor-shell .text-muted {
|
||||
color: rgba(233, 242, 250, 0.78) !important;
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control {
|
||||
background: rgba(10, 18, 28, 0.56);
|
||||
border-color: rgba(255, 255, 255, 0.44);
|
||||
color: #f3f8ff;
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control::placeholder {
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control[readonly],
|
||||
.inline-editor-shell .form-control[disabled] {
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-editor-shell select.form-control option {
|
||||
color: #0f1b2a;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
if ($isInline) {
|
||||
$this->registerCss(<<<CSS
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body > .panel:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.medical-media-select-thumb.is-selected {
|
||||
border-color: #1f8dd6;
|
||||
box-shadow: 0 0 0 2px rgba(31, 141, 214, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="panel panel-default inline-editor-shell">
|
||||
<div class="panel-heading">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;">
|
||||
<span><?= Yii::t('AnimalManagementModule.base', '<strong>Edit</strong> Medical Visit') ?></span>
|
||||
<?php if ($isInline): ?>
|
||||
<span style="display:inline-flex;gap:8px;">
|
||||
<?= Html::submitButton('<i class="fa fa-check"></i>', [
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Save Medical Visit'),
|
||||
'form' => $medicalFormId,
|
||||
]) ?>
|
||||
<?= Html::button('<i class="fa fa-times"></i>', [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'id' => 'medical-inline-cancel-icon',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
|
||||
]) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php
|
||||
$formOptions = ['id' => $medicalFormId, 'enctype' => 'multipart/form-data'];
|
||||
if (!$isInline) {
|
||||
$formOptions['target'] = '_top';
|
||||
}
|
||||
$form = ActiveForm::begin(['options' => $formOptions]);
|
||||
?>
|
||||
<?= Html::hiddenInput('returnTo', (string)($returnTo ?? 'view')) ?>
|
||||
<?= Html::hiddenInput('medicalMediaGalleryPath', $medicalMediaPath, ['id' => 'medical-media-gallery-path']) ?>
|
||||
|
||||
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Visit') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $form->field($model, 'visit_at')->input('datetime-local') ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'provider_name') ?></div>
|
||||
</div>
|
||||
<?= $form->field($model, 'notes')->textarea(['rows' => 3]) ?>
|
||||
<?= $form->field($model, 'recommendations')->textarea(['rows' => 3]) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><?= $renderCustomField('weight', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('pulse', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('blood_pressure', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('oxygen', $model, $customDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Conditions') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('chronic_conditions', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('acute_conditions', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('special_needs', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit Detail') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('date_of_most_recent_medical_visit', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="medical-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($hasMedicalMedia): ?>
|
||||
<img src="<?= Html::encode($medicalMediaPath) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected medical media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#<?= Html::encode($medicalGalleryModalId) ?>" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeMedicalMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Physician') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_business_name', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('physician_street_address', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_city', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_state', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_zip', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_cell_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_business_phone', $model, $customDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_license_number', $model, $customDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Social Post') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $form->field($model, 'post_to_space_feed')->checkbox() ?>
|
||||
<?= $form->field($model, 'post_to_animal_feed')->checkbox() ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($remainingDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($remainingDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $model, $remainingDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Medical Visit'))->submit() ?>
|
||||
<?php if ($isInline): ?>
|
||||
<?= Html::button(Yii::t('AnimalManagementModule.base', 'Cancel'), [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default',
|
||||
'id' => 'medical-inline-cancel',
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
|
||||
->link(($returnTo ?? 'view') === 'medical-visits'
|
||||
? $space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])
|
||||
: $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="<?= Html::encode($medicalGalleryModalId) ?>" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Medical Media from Gallery') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default medical-media-select-thumb<?= $medicalMediaPath === $galleryUrl ? ' is-selected' : '' ?>" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;display:block;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="medicalMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="medicalMediaUpload" name="medicalMediaUpload" form="<?= Html::encode($medicalFormId) ?>" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerJs(<<<JS
|
||||
(function(){
|
||||
function renderMedicalPreview(source) {
|
||||
var preview = $('#medical-media-preview');
|
||||
if (!preview.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
preview.html('<img src="' + source + '" alt="Selected medical media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
} else {
|
||||
preview.html('<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
function markSelectedMedicalThumb(value) {
|
||||
$('.medical-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.medical-media-select-thumb').each(function() {
|
||||
if ($(this).data('media-url') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', '.medical-media-select-thumb', function() {
|
||||
var mediaUrl = $(this).data('media-url');
|
||||
$('#medical-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMedicalThumb(mediaUrl);
|
||||
$('#medicalMediaUpload').val('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
renderMedicalPreview(mediaUrl);
|
||||
}
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
});
|
||||
|
||||
$('#medicalMediaUpload').on('change', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#medical-media-gallery-path').val('');
|
||||
markSelectedMedicalThumb('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
renderMedicalPreview(e.target.result);
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#{$medicalGalleryModalId}').on('shown.bs.modal', function() {
|
||||
markSelectedMedicalThumb($('#medical-media-gallery-path').val());
|
||||
});
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
|
||||
if ($isInline) {
|
||||
$cancelPayload = Json::htmlEncode([
|
||||
'source' => 'animal-inline-editor',
|
||||
'type' => 'cancel',
|
||||
'collapseId' => 'medical-edit-inline-' . (int)$medicalVisit->id,
|
||||
]);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
$(document).on('click', '#medical-inline-cancel, #medical-inline-cancel-icon', function() {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage($cancelPayload, '*');
|
||||
}
|
||||
});
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
}
|
||||
?>
|
||||
424
views/animals/edit-progress-update.php
Normal file
424
views/animals/edit-progress-update.php
Normal file
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\forms\AnimalProgressUpdateForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var AnimalProgressUpdateForm $model */
|
||||
/* @var AnimalProgressUpdate $progressUpdate */
|
||||
/* @var string $returnTo */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
/* @var bool $isInline */
|
||||
|
||||
$isInline = isset($isInline) ? (bool)$isInline : false;
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalProgressUpdateForm $formModel, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$label = (string)$definition['label'];
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalProgressUpdateForm[customFields][$fieldKey]";
|
||||
$fieldValue = $formModel->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$formModel->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalprogressupdateform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$customDefinitions = $model->getCustomFieldDefinitions();
|
||||
$knownProgressKeys = ['progress_notes', 'routine_updates', 'media_reference'];
|
||||
$otherCustomDefinitions = [];
|
||||
foreach ($customDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownProgressKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$otherCustomDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$currentMediaReference = trim((string)($model->customFields['media_reference'] ?? ''));
|
||||
$progressFormId = 'edit-progress-update-form';
|
||||
|
||||
$this->registerCss(<<<CSS
|
||||
.inline-editor-shell.panel {
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 12px;
|
||||
background: rgba(10, 18, 28, 0.36);
|
||||
}
|
||||
|
||||
.inline-editor-shell > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell > .panel-body {
|
||||
background: rgba(10, 18, 28, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell .panel.panel-default {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(10, 18, 28, 0.34);
|
||||
}
|
||||
|
||||
.inline-editor-shell .panel.panel-default > .panel-heading {
|
||||
color: #eef5fb;
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.inline-editor-shell,
|
||||
.inline-editor-shell .panel-body,
|
||||
.inline-editor-shell .control-label,
|
||||
.inline-editor-shell .checkbox label,
|
||||
.inline-editor-shell .radio label,
|
||||
.inline-editor-shell .help-block {
|
||||
color: #eef5fb;
|
||||
}
|
||||
|
||||
.inline-editor-shell .text-muted {
|
||||
color: rgba(233, 242, 250, 0.78) !important;
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control {
|
||||
background: rgba(10, 18, 28, 0.56);
|
||||
border-color: rgba(255, 255, 255, 0.44);
|
||||
color: #f3f8ff;
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control::placeholder {
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-editor-shell .form-control[readonly],
|
||||
.inline-editor-shell .form-control[disabled] {
|
||||
background: rgba(10, 18, 28, 0.42);
|
||||
color: rgba(243, 248, 255, 0.72);
|
||||
}
|
||||
|
||||
.inline-editor-shell select.form-control option {
|
||||
color: #0f1b2a;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
if ($isInline) {
|
||||
$this->registerCss(<<<CSS
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body > .panel:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
CSS
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="panel panel-default inline-editor-shell">
|
||||
<div class="panel-heading">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;">
|
||||
<span><?= Yii::t('AnimalManagementModule.base', '<strong>Edit</strong> Progress Update') ?></span>
|
||||
<?php if ($isInline): ?>
|
||||
<span style="display:inline-flex;gap:8px;">
|
||||
<?= Html::submitButton('<i class="fa fa-check"></i>', [
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Save Progress Update'),
|
||||
'form' => $progressFormId,
|
||||
]) ?>
|
||||
<?= Html::button('<i class="fa fa-times"></i>', [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default btn-sm',
|
||||
'id' => 'progress-inline-cancel-icon',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
|
||||
]) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php
|
||||
$formOptions = ['id' => $progressFormId, 'enctype' => 'multipart/form-data'];
|
||||
if (!$isInline) {
|
||||
$formOptions['target'] = '_top';
|
||||
}
|
||||
$form = ActiveForm::begin(['options' => $formOptions]);
|
||||
?>
|
||||
<?= Html::hiddenInput('returnTo', (string)($returnTo ?? 'view')) ?>
|
||||
|
||||
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Progress Update') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><?= $form->field($model, 'weight') ?></div>
|
||||
<div class="col-sm-8"><?= $form->field($model, 'vitals')->textInput(['maxlength' => 255]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'behavior_notes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'medical_concerns')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'meal_plan_changes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $form->field($model, 'housing_changes')->textarea(['rows' => 2]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Notes') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('progress_notes', $model, $customDefinitions) ?>
|
||||
<?= $renderCustomField('routine_updates', $model, $customDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($otherCustomDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($otherCustomDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $model, $otherCustomDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<input type="hidden" id="progress-media-gallery-path" name="progressMediaGalleryPath" value="<?= Html::encode($currentMediaReference) ?>">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="progress-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($currentMediaReference !== '' && (preg_match('/^https?:\/\//i', $currentMediaReference) || substr($currentMediaReference, 0, 1) === '/')): ?>
|
||||
<img src="<?= Html::encode($currentMediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#progress-media-modal" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeProgressMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Social Post') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $form->field($model, 'post_to_space_feed')->checkbox() ?>
|
||||
<?= $form->field($model, 'post_to_animal_feed')->checkbox() ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="progress-media-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Progress Media') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default progress-media-select-thumb<?= $currentMediaReference === $galleryUrl ? ' is-selected' : '' ?>" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;border-radius:4px;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="progressMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="progressMediaUpload" name="progressMediaUpload" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Progress Update'))->submit() ?>
|
||||
<?php if ($isInline): ?>
|
||||
<?= Html::button(Yii::t('AnimalManagementModule.base', 'Cancel'), [
|
||||
'type' => 'button',
|
||||
'class' => 'btn btn-default',
|
||||
'id' => 'progress-inline-cancel',
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
|
||||
->link(($returnTo ?? 'view') === 'progress-updates'
|
||||
? $space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])
|
||||
: $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerCss(<<<CSS
|
||||
.progress-media-select-thumb.is-selected {
|
||||
border-color: #2f7df4 !important;
|
||||
box-shadow: 0 0 0 2px rgba(47, 125, 244, 0.22);
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
(function() {
|
||||
function renderProgressPreview(source) {
|
||||
var preview = $('#progress-media-preview');
|
||||
if (!preview.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
preview.html('<img src="' + source + '" alt="Selected media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
} else {
|
||||
preview.html('<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
function markSelectedMediaThumb(value) {
|
||||
$('.progress-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.progress-media-select-thumb').each(function() {
|
||||
if (($(this).attr('data-media-url') || '') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).off('click.editProgressMediaSelect', '.progress-media-select-thumb').on('click.editProgressMediaSelect', '.progress-media-select-thumb', function() {
|
||||
var mediaUrl = $(this).attr('data-media-url') || '';
|
||||
$('#progress-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMediaThumb(mediaUrl);
|
||||
$('#progressMediaUpload').val('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
renderProgressPreview(mediaUrl);
|
||||
}
|
||||
$('#progress-media-modal').modal('hide');
|
||||
});
|
||||
|
||||
$(document).off('change.editProgressMediaUpload', '#progressMediaUpload').on('change.editProgressMediaUpload', '#progressMediaUpload', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#progress-media-gallery-path').val('');
|
||||
markSelectedMediaThumb('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
renderProgressPreview(e.target.result);
|
||||
$('#progress-media-modal').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$(document).off('shown.bs.modal.editProgressMediaModal', '#progress-media-modal').on('shown.bs.modal.editProgressMediaModal', '#progress-media-modal', function() {
|
||||
markSelectedMediaThumb($('#progress-media-gallery-path').val());
|
||||
});
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
|
||||
if ($isInline) {
|
||||
$cancelPayload = Json::htmlEncode([
|
||||
'source' => 'animal-inline-editor',
|
||||
'type' => 'cancel',
|
||||
'collapseId' => 'progress-edit-inline-' . (int)$progressUpdate->id,
|
||||
]);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
$(document).on('click', '#progress-inline-cancel, #progress-inline-cancel-icon', function() {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage($cancelPayload, '*');
|
||||
}
|
||||
});
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
}
|
||||
?>
|
||||
283
views/animals/index.php
Normal file
283
views/animals/index.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Animal[] $animals */
|
||||
/* @var AnimalTransfer[] $incomingTransfers */
|
||||
/* @var AnimalTransfer[] $outgoingTransfers */
|
||||
/* @var string $queryValue */
|
||||
/* @var string $statusFilter */
|
||||
/* @var string $speciesFilter */
|
||||
/* @var string $viewMode */
|
||||
/* @var string $sortKey */
|
||||
/* @var string $sortDirection */
|
||||
/* @var array $availableColumns */
|
||||
/* @var array $selectedColumns */
|
||||
/* @var array $speciesOptions */
|
||||
/* @var array<int, AnimalMedicalVisit> $latestMedicalVisitByAnimal */
|
||||
/* @var array<int, string> $animalImageUrls */
|
||||
/* @var array<int, string> $transferAnimalImageUrls */
|
||||
/* @var array $tileFields */
|
||||
/* @var array<int, array> $tileFieldOverrides */
|
||||
/* @var Space $space */
|
||||
/* @var bool $canManage */
|
||||
|
||||
$currentParams = [
|
||||
'q' => $queryValue,
|
||||
'status' => $statusFilter,
|
||||
'species' => $speciesFilter,
|
||||
'view' => $viewMode,
|
||||
'sort' => $sortKey,
|
||||
'direction' => $sortDirection,
|
||||
'cols' => $selectedColumns,
|
||||
];
|
||||
|
||||
$buildUrl = static function (array $overrides) use ($space, $currentParams): string {
|
||||
$params = array_merge($currentParams, $overrides);
|
||||
return $space->createUrl('/animal_management/animals/index', $params);
|
||||
};
|
||||
|
||||
$sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirection): string {
|
||||
$nextDirection = ($sortKey === $column && $sortDirection === 'asc') ? 'desc' : 'asc';
|
||||
return $buildUrl(['sort' => $column, 'direction' => $nextDirection, 'view' => 'table']);
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<span><?= Yii::t('AnimalManagementModule.base', '<strong>Animals</strong>') ?></span>
|
||||
<span style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<?= Html::a('<i class="fa fa-th-large"></i>', $buildUrl(['view' => 'tiles']), [
|
||||
'class' => 'btn btn-default btn-sm' . ($viewMode === 'tiles' ? ' active' : ''),
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Tile View'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Tile View'),
|
||||
]) ?>
|
||||
<?= Html::a('<i class="fa fa-table"></i>', $buildUrl(['view' => 'table']), [
|
||||
'class' => 'btn btn-default btn-sm' . ($viewMode === 'table' ? ' active' : ''),
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Table View'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Table View'),
|
||||
]) ?>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Intake'), $space->createUrl('/animal_management/animals/create'), [
|
||||
'class' => 'btn btn-primary btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Intake'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Intake'),
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<form method="get" action="<?= Html::encode($space->createUrl('/animal_management/animals/index')) ?>" class="form-inline" style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap;">
|
||||
<input type="hidden" name="view" value="<?= Html::encode($viewMode) ?>">
|
||||
<input type="hidden" name="sort" value="<?= Html::encode($sortKey) ?>">
|
||||
<input type="hidden" name="direction" value="<?= Html::encode($sortDirection) ?>">
|
||||
<?php foreach ($selectedColumns as $col): ?>
|
||||
<input type="hidden" name="cols[]" value="<?= Html::encode($col) ?>">
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="form-group" style="flex:1;min-width:260px;">
|
||||
<input type="text" class="form-control" style="width:100%;" name="q" value="<?= Html::encode($queryValue) ?>" placeholder="<?= Yii::t('AnimalManagementModule.base', 'Search by name, species, ID, or breed') ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<?= Html::dropDownList('status', $statusFilter, ['' => Yii::t('AnimalManagementModule.base', 'All Statuses')] + Animal::statusOptions(), ['class' => 'form-control']) ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<?php $speciesDropDown = ['' => Yii::t('AnimalManagementModule.base', 'All Species')]; ?>
|
||||
<?php foreach ($speciesOptions as $speciesOption): ?>
|
||||
<?php $speciesDropDown[$speciesOption] = $speciesOption; ?>
|
||||
<?php endforeach; ?>
|
||||
<?= Html::dropDownList('species', $speciesFilter, $speciesDropDown, ['class' => 'form-control']) ?>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default"><?= Yii::t('AnimalManagementModule.base', 'Apply') ?></button>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Reset'))->link($space->createUrl('/animal_management/animals/index', ['view' => $viewMode])) ?>
|
||||
</form>
|
||||
|
||||
<?php if ($viewMode === 'table'): ?>
|
||||
<details style="margin-bottom:12px;">
|
||||
<summary style="cursor:pointer;"><?= Yii::t('AnimalManagementModule.base', 'Show/Hide Columns') ?></summary>
|
||||
<form method="get" action="<?= Html::encode($space->createUrl('/animal_management/animals/index')) ?>" style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<input type="hidden" name="view" value="table">
|
||||
<input type="hidden" name="q" value="<?= Html::encode($queryValue) ?>">
|
||||
<input type="hidden" name="status" value="<?= Html::encode($statusFilter) ?>">
|
||||
<input type="hidden" name="species" value="<?= Html::encode($speciesFilter) ?>">
|
||||
<input type="hidden" name="sort" value="<?= Html::encode($sortKey) ?>">
|
||||
<input type="hidden" name="direction" value="<?= Html::encode($sortDirection) ?>">
|
||||
<?php foreach ($availableColumns as $columnKey => $columnLabel): ?>
|
||||
<label style="margin:0 8px 0 0;">
|
||||
<input type="checkbox" name="cols[]" value="<?= Html::encode($columnKey) ?>" <?= in_array($columnKey, $selectedColumns, true) ? 'checked' : '' ?> <?= $columnKey === 'name' ? 'disabled' : '' ?>>
|
||||
<?= Html::encode($columnLabel) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<button type="submit" class="btn btn-default btn-sm"><?= Yii::t('AnimalManagementModule.base', 'Update Columns') ?></button>
|
||||
</form>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($animals)): ?>
|
||||
<div class="alert alert-info" style="margin-bottom:0;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'No animal profiles yet. Create the first intake record to begin tracking.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php if ($viewMode === 'tiles'): ?>
|
||||
<div class="row">
|
||||
<?php foreach ($animals as $animal): ?>
|
||||
<?php $animalId = (int)$animal->id; ?>
|
||||
<?php $lastMedical = $latestMedicalVisitByAnimal[$animalId] ?? null; ?>
|
||||
<div class="col-sm-6 col-md-4" style="margin-bottom:16px;">
|
||||
<?= $this->render('_tile', [
|
||||
'animal' => $animal,
|
||||
'contentContainer' => $space,
|
||||
'lastMedical' => $lastMedical,
|
||||
'imageUrl' => $animalImageUrls[$animalId] ?? '',
|
||||
'tileFields' => $tileFieldOverrides[$animalId] ?? $tileFields,
|
||||
'showMedicalIcon' => true,
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if (in_array('animal_uid', $selectedColumns, true)): ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('animal_uid')) ?>"><?= Yii::t('AnimalManagementModule.base', 'ID') ?></a></th>
|
||||
<?php endif; ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('name')) ?>"><?= Yii::t('AnimalManagementModule.base', 'Name') ?></a></th>
|
||||
<?php if (in_array('species', $selectedColumns, true)): ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('species')) ?>"><?= Yii::t('AnimalManagementModule.base', 'Species') ?></a></th>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('status', $selectedColumns, true)): ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('status')) ?>"><?= Yii::t('AnimalManagementModule.base', 'Status') ?></a></th>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('last_medical', $selectedColumns, true)): ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('last_medical')) ?>"><?= Yii::t('AnimalManagementModule.base', 'Last Medical Visit') ?></a></th>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('updated_at', $selectedColumns, true)): ?>
|
||||
<th><a href="<?= Html::encode($sortUrl('updated_at')) ?>"><?= Yii::t('AnimalManagementModule.base', 'Updated') ?></a></th>
|
||||
<?php endif; ?>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($animals as $animal): ?>
|
||||
<?php $lastMedical = $latestMedicalVisitByAnimal[(int)$animal->id] ?? null; ?>
|
||||
<tr>
|
||||
<?php if (in_array('animal_uid', $selectedColumns, true)): ?>
|
||||
<td><?= Html::encode($animal->animal_uid) ?></td>
|
||||
<?php endif; ?>
|
||||
<td>
|
||||
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>">
|
||||
<?= Html::encode($animal->getDisplayName()) ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php if (in_array('species', $selectedColumns, true)): ?>
|
||||
<td><?= Html::encode((string)$animal->species) ?></td>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('status', $selectedColumns, true)): ?>
|
||||
<td><?= Html::encode(Animal::statusOptions()[$animal->status] ?? $animal->status) ?></td>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('last_medical', $selectedColumns, true)): ?>
|
||||
<td>
|
||||
<?php if ($lastMedical instanceof AnimalMedicalVisit): ?>
|
||||
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id]) . '#medical-visit-' . (int)$lastMedical->id) ?>">
|
||||
<?= Html::encode(DateDisplayHelper::format((string)$lastMedical->visit_at)) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array('updated_at', $selectedColumns, true)): ?>
|
||||
<td><?= Html::encode(DateDisplayHelper::format((string)$animal->updated_at)) ?></td>
|
||||
<?php endif; ?>
|
||||
<td class="text-right" style="white-space:nowrap;">
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'View'))
|
||||
->link($space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Medical'))
|
||||
->link($space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])) ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Progress'))
|
||||
->link($space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])) ?>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Edit'))
|
||||
->link($space->createUrl('/animal_management/animals/edit', ['id' => $animal->id])) ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Transfer'))
|
||||
->link($space->createUrl('/animal_management/animals/transfer', ['id' => $animal->id])) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<hr>
|
||||
|
||||
<h4 id="incoming-transfers" style="margin-top:0;"><?= Yii::t('AnimalManagementModule.base', 'Incoming Transfer Requests') ?></h4>
|
||||
<?php if (empty($incomingTransfers)): ?>
|
||||
<div class="text-muted" style="margin-bottom:12px;"><?= Yii::t('AnimalManagementModule.base', 'No incoming requests.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="margin-bottom:4px;">
|
||||
<?php foreach ($incomingTransfers as $transfer): ?>
|
||||
<?php
|
||||
$fromSpace = $transfer->getFromSpace();
|
||||
$toSpace = $transfer->getToSpace();
|
||||
$animalLinkSpace = ($transfer->status === AnimalTransfer::STATUS_COMPLETED)
|
||||
? ($toSpace ?: $fromSpace)
|
||||
: ($fromSpace ?: $toSpace);
|
||||
?>
|
||||
<div class="col-sm-6" style="margin-bottom:14px;">
|
||||
<?= $this->render('_transfer_tile', [
|
||||
'transfer' => $transfer,
|
||||
'space' => $space,
|
||||
'otherRescueName' => $fromSpace ? $fromSpace->name : Yii::t('AnimalManagementModule.base', 'Unknown Rescue'),
|
||||
'otherRescueUrl' => $fromSpace ? $fromSpace->createUrl('/space/space/home') : '',
|
||||
'animalProfileUrl' => $animalLinkSpace ? $animalLinkSpace->createUrl('/animal_management/animals/view', ['id' => $transfer->animal_id]) : '',
|
||||
'imageUrl' => trim((string)($transferAnimalImageUrls[(int)$transfer->animal_id] ?? '')),
|
||||
'isIncoming' => true,
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 id="outgoing-transfers"><?= Yii::t('AnimalManagementModule.base', 'Outgoing Transfer History') ?></h4>
|
||||
<?php if (empty($outgoingTransfers)): ?>
|
||||
<div class="text-muted"><?= Yii::t('AnimalManagementModule.base', 'No outgoing transfers yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="margin-bottom:0;">
|
||||
<?php foreach ($outgoingTransfers as $transfer): ?>
|
||||
<?php
|
||||
$fromSpace = $transfer->getFromSpace();
|
||||
$toSpace = $transfer->getToSpace();
|
||||
$animalLinkSpace = ($transfer->status === AnimalTransfer::STATUS_COMPLETED)
|
||||
? ($toSpace ?: $fromSpace)
|
||||
: ($fromSpace ?: $toSpace);
|
||||
?>
|
||||
<div class="col-sm-6 col-md-4" style="margin-bottom:14px;">
|
||||
<?= $this->render('_transfer_tile', [
|
||||
'transfer' => $transfer,
|
||||
'space' => $space,
|
||||
'otherRescueName' => $toSpace ? $toSpace->name : Yii::t('AnimalManagementModule.base', 'Unknown Rescue'),
|
||||
'otherRescueUrl' => $toSpace ? $toSpace->createUrl('/space/space/home') : '',
|
||||
'animalProfileUrl' => $animalLinkSpace ? $animalLinkSpace->createUrl('/animal_management/animals/view', ['id' => $transfer->animal_id]) : '',
|
||||
'imageUrl' => trim((string)($transferAnimalImageUrls[(int)$transfer->animal_id] ?? '')),
|
||||
'isIncoming' => false,
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
34
views/animals/inline-edit-result.php
Normal file
34
views/animals/inline-edit-result.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var string $collapseId */
|
||||
/* @var array $refreshSelectors */
|
||||
|
||||
$payload = [
|
||||
'source' => 'animal-inline-editor',
|
||||
'type' => 'saved',
|
||||
'collapseId' => (string)$collapseId,
|
||||
'refreshSelectors' => array_values(array_map('strval', $refreshSelectors ?? [])),
|
||||
];
|
||||
|
||||
$jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
|
||||
<div class="panel panel-success" style="margin:10px;">
|
||||
<div class="panel-body" style="padding:12px;">
|
||||
<?= Html::encode(Yii::t('AnimalManagementModule.base', 'Saved. Updating section...')) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerJs(<<<JS
|
||||
(function() {
|
||||
var payload = {$jsonPayload};
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage(payload, '*');
|
||||
}
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
?>
|
||||
901
views/animals/medical-visits.php
Normal file
901
views/animals/medical-visits.php
Normal file
@@ -0,0 +1,901 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\forms\AnimalMedicalVisitForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var bool $canManage */
|
||||
/* @var AnimalMedicalVisitForm $medicalVisitForm */
|
||||
/* @var AnimalMedicalVisit[] $medicalVisits */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
|
||||
$hiddenMedicalKeys = [
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
];
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalMedicalVisitForm $model, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$vitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
$label = (string)($vitalLabelOverrides[$fieldKey] ?? $definition['label']);
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalMedicalVisitForm[customFields][$fieldKey]";
|
||||
$fieldValue = $model->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$model->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalmedicalvisitform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalmedicalvisitform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalmedicalvisitform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$medicalCustomDefinitions = $medicalVisitForm->getCustomFieldDefinitions();
|
||||
$knownMedicalKeys = [
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'medical_media_reference',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$remainingMedicalDefinitions = [];
|
||||
foreach ($medicalCustomDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true) || in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$remainingMedicalDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$newMedicalMediaPath = trim((string)($medicalVisitForm->customFields['medical_media_reference'] ?? $medicalVisitForm->customFields['media_reference'] ?? ''));
|
||||
$hasNewMedicalMedia = $newMedicalMediaPath !== '' && (preg_match('/^https?:\/\//i', $newMedicalMediaPath) || substr($newMedicalMediaPath, 0, 1) === '/');
|
||||
$medicalGalleryModalId = 'medical-media-gallery-modal';
|
||||
$medicalAddModalId = 'add-medical-visit-modal';
|
||||
$medicalFormId = 'add-medical-visit-form';
|
||||
$openMedicalEditId = (int)Yii::$app->request->get('inlineMedicalEdit', 0);
|
||||
$openMedicalAdd = (int)Yii::$app->request->get('inlineMedicalAdd', 0) === 1;
|
||||
$medicalVitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
?>
|
||||
|
||||
<style>
|
||||
.medical-feed-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #d5dfe8;
|
||||
border-radius: 12px;
|
||||
background: #223446;
|
||||
margin-bottom: 12px;
|
||||
min-height: 260px;
|
||||
box-shadow: 0 8px 22px rgba(12, 24, 36, 0.16);
|
||||
}
|
||||
|
||||
.medical-feed-cover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.medical-feed-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(110deg, rgba(10, 18, 28, 0.28) 10%, rgba(10, 18, 28, 0.55) 52%, rgba(10, 18, 28, 0.75) 100%);
|
||||
}
|
||||
|
||||
.medical-feed-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 260px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.medical-feed-top-row {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.medical-feed-text-wrap {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
margin-left: auto;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
background: rgba(10, 18, 28, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
padding: 12px;
|
||||
color: #ecf2f8;
|
||||
}
|
||||
|
||||
.medical-feed-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.medical-feed-date {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.medical-feed-text-wrap {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.medical-feed-text-wrap {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.medical-chip-row--top {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.medical-chip-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.medical-chip-row--top {
|
||||
margin-bottom: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.medical-chip {
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.medical-media-select-thumb.is-selected {
|
||||
border-color: #1f8dd6;
|
||||
box-shadow: 0 0 0 2px rgba(31, 141, 214, 0.2);
|
||||
}
|
||||
|
||||
.medical-section-label {
|
||||
font-size: 11px;
|
||||
letter-spacing: .04em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(231, 241, 249, 0.78);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.medical-section-body {
|
||||
color: #eff5fb;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.medical-feed-inline-editor {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
margin: 0 14px 14px 14px;
|
||||
padding-top: 0;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.medical-feed-inline-editor iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.medical-feed-inline-editor {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.medical-feed-inline-editor {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="medical-visits-page" class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;align-items:center;">
|
||||
<span style="font-size:20px;line-height:1.2;"><?= Yii::t('AnimalManagementModule.base', '<strong>Medical Visits</strong>') ?></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin-bottom:12px;">
|
||||
<?= Html::a(
|
||||
Html::encode($animal->getDisplayName()),
|
||||
$space->createUrl('/animal_management/animals/view', ['id' => $animal->id]),
|
||||
['style' => 'font-size:20px;font-weight:700;line-height:1.2;']
|
||||
) ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'All Animals'),
|
||||
$space->createUrl('/animal_management/animals/index'),
|
||||
['style' => 'font-size:16px;line-height:1.2;']
|
||||
) ?>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add Medical Visit'), '#medical-add-inline', [
|
||||
'class' => 'btn btn-success btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Add Medical Visit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Add Medical Visit'),
|
||||
'data-toggle' => 'collapse',
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="medical-add-inline" class="collapse medical-feed-inline-editor<?= $openMedicalAdd ? ' in' : '' ?>" style="margin:0 0 14px 0;">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/add-medical-visit-inline', ['id' => $animal->id, 'inline' => 1, 'returnTo' => 'medical-visits', '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:860px;border:1px solid rgba(255,255,255,0.22);border-radius:10px;background:transparent;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($medicalVisits)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No medical visits recorded.') ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($medicalVisits as $visit): ?>
|
||||
<?php
|
||||
$visitCustomValues = $visit->getCustomFieldDisplayValues($canManage);
|
||||
$visitFieldsByKey = [];
|
||||
$additionalVisitFields = [];
|
||||
$medicalMedia = '';
|
||||
foreach ($visitCustomValues as $customField) {
|
||||
$fieldKey = (string)($customField['field_key'] ?? '');
|
||||
if (in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldValue = trim((string)($customField['value'] ?? ''));
|
||||
if ($fieldValue === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fieldKey === 'medical_media_reference' || $fieldKey === 'media_reference') {
|
||||
$medicalMedia = $fieldValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true)) {
|
||||
$visitFieldsByKey[$fieldKey] = [
|
||||
'label' => (string)($medicalVitalLabelOverrides[$fieldKey] ?? ($customField['label'] ?? $fieldKey)),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$additionalVisitFields[] = [
|
||||
'label' => (string)($customField['label'] ?? $fieldKey),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
}
|
||||
|
||||
$hasMedicalMedia = $medicalMedia !== '' && (preg_match('/^https?:\/\//i', $medicalMedia) || substr($medicalMedia, 0, 1) === '/');
|
||||
$visitDateDisplay = DateDisplayHelper::format((string)$visit->visit_at);
|
||||
$vitalKeys = ['weight', 'pulse', 'blood_pressure', 'oxygen'];
|
||||
$hasVitals = false;
|
||||
foreach ($vitalKeys as $vitalKey) {
|
||||
if (!empty($visitFieldsByKey[$vitalKey]['value'])) {
|
||||
$hasVitals = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div id="medical-visit-<?= (int)$visit->id ?>" class="medical-feed-card">
|
||||
<?php if ($hasMedicalMedia): ?>
|
||||
<img class="medical-feed-cover" src="<?= Html::encode($medicalMedia) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Medical media') ?>">
|
||||
<?php endif; ?>
|
||||
<div class="medical-feed-overlay"></div>
|
||||
<div class="medical-feed-content">
|
||||
<div class="medical-feed-top-row">
|
||||
<span class="medical-feed-date"><?= Html::encode($visitDateDisplay !== '' ? $visitDateDisplay : (string)$visit->visit_at) ?></span>
|
||||
<?php if ($hasVitals): ?>
|
||||
<div class="medical-chip-row medical-chip-row--top">
|
||||
<?php foreach ($vitalKeys as $vitalKey): ?>
|
||||
<?php if (empty($visitFieldsByKey[$vitalKey]['value'])) { continue; } ?>
|
||||
<span class="medical-chip">
|
||||
<?= Html::encode($visitFieldsByKey[$vitalKey]['label']) ?>: <?= Html::encode($visitFieldsByKey[$vitalKey]['value']) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="medical-feed-text-wrap">
|
||||
<div class="medical-feed-header">
|
||||
<div>
|
||||
<?php if (!empty($visit->provider_name)): ?>
|
||||
<div style="font-size:12px;color:rgba(239,245,251,0.86);margin-top:2px;"><?= Yii::t('AnimalManagementModule.base', 'Provider') ?>: <?= Html::encode((string)$visit->provider_name) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a(
|
||||
'<i class="fa fa-pencil"></i>',
|
||||
'#medical-edit-inline-' . (int)$visit->id,
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-toggle' => 'collapse',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($visit->notes)): ?>
|
||||
<div class="medical-section-label"><?= Yii::t('AnimalManagementModule.base', 'Clinical Notes') ?></div>
|
||||
<div class="medical-section-body"><?= nl2br(Html::encode((string)$visit->notes)) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($visit->recommendations)): ?>
|
||||
<div class="medical-section-label"><?= Yii::t('AnimalManagementModule.base', 'Recommendations') ?></div>
|
||||
<div class="medical-section-body"><?= nl2br(Html::encode((string)$visit->recommendations)) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$conditionKeys = ['chronic_conditions', 'acute_conditions', 'special_needs'];
|
||||
foreach ($conditionKeys as $conditionKey):
|
||||
if (empty($visitFieldsByKey[$conditionKey]['value'])) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="medical-section-label"><?= Html::encode($visitFieldsByKey[$conditionKey]['label']) ?></div>
|
||||
<div class="medical-section-body"><?= nl2br(Html::encode($visitFieldsByKey[$conditionKey]['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php
|
||||
$contactKeys = [
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
];
|
||||
$hasContact = false;
|
||||
foreach ($contactKeys as $contactKey) {
|
||||
if (!empty($visitFieldsByKey[$contactKey]['value'])) {
|
||||
$hasContact = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($hasContact): ?>
|
||||
<?php
|
||||
$contactLines = [];
|
||||
if (!empty($visitFieldsByKey['physician_name']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_name']['value'];
|
||||
}
|
||||
if (!empty($visitFieldsByKey['physician_business_name']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_business_name']['value'];
|
||||
}
|
||||
if (!empty($visitFieldsByKey['physician_street_address']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_street_address']['value'];
|
||||
}
|
||||
|
||||
$cityStateZip = trim(implode(', ', array_filter([
|
||||
trim((string)($visitFieldsByKey['physician_city']['value'] ?? '')),
|
||||
trim((string)($visitFieldsByKey['physician_state']['value'] ?? '')),
|
||||
])));
|
||||
$zipValue = trim((string)($visitFieldsByKey['physician_zip']['value'] ?? ''));
|
||||
if ($zipValue !== '') {
|
||||
$cityStateZip = trim($cityStateZip . ($cityStateZip !== '' ? ' ' : '') . $zipValue);
|
||||
}
|
||||
if ($cityStateZip !== '') {
|
||||
$contactLines[] = $cityStateZip;
|
||||
}
|
||||
|
||||
$phones = array_filter([
|
||||
trim((string)($visitFieldsByKey['physician_cell_phone']['value'] ?? '')),
|
||||
trim((string)($visitFieldsByKey['physician_business_phone']['value'] ?? '')),
|
||||
]);
|
||||
if (!empty($phones)) {
|
||||
$contactLines[] = implode(' · ', $phones);
|
||||
}
|
||||
|
||||
if (!empty($visitFieldsByKey['physician_license_number']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_license_number']['value'];
|
||||
}
|
||||
?>
|
||||
<div class="medical-section-label"><?= Yii::t('AnimalManagementModule.base', 'Care Contact') ?></div>
|
||||
<div class="medical-section-body">
|
||||
<?php foreach ($contactLines as $contactLine): ?>
|
||||
<div><?= Html::encode($contactLine) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($visitFieldsByKey['date_of_most_recent_medical_visit']['value'])): ?>
|
||||
<div class="medical-section-label"><?= Html::encode($visitFieldsByKey['date_of_most_recent_medical_visit']['label']) ?></div>
|
||||
<div class="medical-section-body"><?= Html::encode(DateDisplayHelper::format((string)$visitFieldsByKey['date_of_most_recent_medical_visit']['value'])) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($additionalVisitFields)): ?>
|
||||
<div class="medical-section-label"><?= Yii::t('AnimalManagementModule.base', 'Additional Fields') ?></div>
|
||||
<div class="medical-section-body">
|
||||
<?php foreach ($additionalVisitFields as $additionalField): ?>
|
||||
<div><strong><?= Html::encode((string)$additionalField['label']) ?>:</strong> <?= nl2br(Html::encode((string)$additionalField['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="medical-edit-inline-<?= (int)$visit->id ?>" class="collapse medical-feed-inline-editor<?= $openMedicalEditId === (int)$visit->id ? ' in' : '' ?>">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/edit-medical-visit', ['id' => $animal->id, 'visitId' => $visit->id, 'inline' => 1, 'returnTo' => 'medical-visits', '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:760px;border:1px solid rgba(255,255,255,0.22);border-radius:10px;background:transparent;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div class="modal fade" id="<?= Html::encode($medicalAddModalId) ?>" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Add Medical Visit') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php $medicalForm = ActiveForm::begin([
|
||||
'action' => $space->createUrl('/animal_management/animals/add-medical-visit', ['id' => $animal->id]),
|
||||
'options' => ['id' => $medicalFormId, 'enctype' => 'multipart/form-data'],
|
||||
]); ?>
|
||||
<?= Html::hiddenInput('returnTo', 'medical-visits') ?>
|
||||
<?= Html::hiddenInput('medicalMediaGalleryPath', $newMedicalMediaPath, ['id' => 'medical-media-gallery-path']) ?>
|
||||
<?= $medicalForm->errorSummary($medicalVisitForm, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Visit') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $medicalForm->field($medicalVisitForm, 'visit_at')->input('datetime-local') ?></div>
|
||||
<div class="col-sm-6"><?= $medicalForm->field($medicalVisitForm, 'provider_name') ?></div>
|
||||
</div>
|
||||
<?= $medicalForm->field($medicalVisitForm, 'notes')->textarea(['rows' => 3]) ?>
|
||||
<?= $medicalForm->field($medicalVisitForm, 'recommendations')->textarea(['rows' => 3]) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><?= $renderCustomField('weight', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('pulse', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('blood_pressure', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-3"><?= $renderCustomField('oxygen', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Conditions') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('chronic_conditions', $medicalVisitForm, $medicalCustomDefinitions) ?>
|
||||
<?= $renderCustomField('acute_conditions', $medicalVisitForm, $medicalCustomDefinitions) ?>
|
||||
<?= $renderCustomField('special_needs', $medicalVisitForm, $medicalCustomDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit Detail') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('date_of_most_recent_medical_visit', $medicalVisitForm, $medicalCustomDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="medical-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($hasNewMedicalMedia): ?>
|
||||
<img src="<?= Html::encode($newMedicalMediaPath) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected medical media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#<?= Html::encode($medicalGalleryModalId) ?>" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeMedicalMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Physician') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_name', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-6"><?= $renderCustomField('physician_business_name', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-12"><?= $renderCustomField('physician_street_address', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_city', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_state', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_zip', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_cell_phone', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_business_phone', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
<div class="col-sm-4"><?= $renderCustomField('physician_license_number', $medicalVisitForm, $medicalCustomDefinitions) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($remainingMedicalDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($remainingMedicalDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $medicalVisitForm, $remainingMedicalDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Add Medical Visit'))->submit() ?>
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div class="modal fade" id="<?= Html::encode($medicalGalleryModalId) ?>" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Medical Media from Gallery') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default medical-media-select-thumb<?= $newMedicalMediaPath === $galleryUrl ? ' is-selected' : '' ?>" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;display:block;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="medicalMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="medicalMediaUpload" name="medicalMediaUpload" form="<?= Html::encode($medicalFormId) ?>" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerJs(<<<JS
|
||||
(function(){
|
||||
var pageRootSelector = '#medical-visits-page';
|
||||
var formSelector = '#{$medicalFormId}';
|
||||
|
||||
function scrollInlineEditorIntoView(editor) {
|
||||
var editorNode = $(editor);
|
||||
if (!editorNode.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sideSpacing = parseFloat(editorNode.css('margin-left'));
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = parseFloat(editorNode.closest('.panel-body').css('padding-left'));
|
||||
}
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = 14;
|
||||
}
|
||||
|
||||
var fixedHeaderHeight = 0;
|
||||
$('.navbar-fixed-top:visible, #topbar:visible, .topbar:visible, .layout-top-container:visible').each(function() {
|
||||
var h = $(this).outerHeight() || 0;
|
||||
if (h > fixedHeaderHeight) {
|
||||
fixedHeaderHeight = h;
|
||||
}
|
||||
});
|
||||
|
||||
var topReserve = Math.max(sideSpacing, 14) + Math.max(fixedHeaderHeight, 64) + 28;
|
||||
var top = Math.max(0, editorNode.offset().top - topReserve);
|
||||
$('html, body').stop(true).animate({scrollTop: top}, 220);
|
||||
}
|
||||
|
||||
function refreshMedicalVisitsPageRoot() {
|
||||
return $.get(window.location.href).done(function(html) {
|
||||
var doc = $('<div></div>').append($.parseHTML(html, document, true));
|
||||
var nextRoot = doc.find(pageRootSelector).first();
|
||||
if (!nextRoot.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(pageRootSelector).replaceWith(nextRoot);
|
||||
if (typeof window.initMedicalVisitsPage === 'function') {
|
||||
window.initMedicalVisitsPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.__animalMedicalVisitsInlineListenerBound) {
|
||||
window.__animalMedicalVisitsInlineListenerBound = true;
|
||||
window.addEventListener('message', function(event) {
|
||||
var data = event.data || {};
|
||||
if (!data || typeof data !== 'object' || data.source !== 'animal-inline-editor') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'cancel') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'saved') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
refreshMedicalVisitsPageRoot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.initMedicalVisitsPage = function() {
|
||||
$(document)
|
||||
.off('shown.bs.collapse.medicalInlineScroll', pageRootSelector + ' .medical-feed-inline-editor')
|
||||
.on('shown.bs.collapse.medicalInlineScroll', pageRootSelector + ' .medical-feed-inline-editor', function() {
|
||||
scrollInlineEditorIntoView(this);
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('click.medicalInlineScroll', pageRootSelector + ' a[href^="#medical-edit-inline-"], ' + pageRootSelector + ' a[href="#medical-add-inline"]')
|
||||
.on('click.medicalInlineScroll', pageRootSelector + ' a[href^="#medical-edit-inline-"], ' + pageRootSelector + ' a[href="#medical-add-inline"]', function() {
|
||||
var target = $(this).attr('href');
|
||||
if (!target || target.charAt(0) !== '#') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.setTimeout(function() {
|
||||
scrollInlineEditorIntoView($(target));
|
||||
}, 260);
|
||||
});
|
||||
|
||||
var preopenedEditor = $(pageRootSelector + ' .medical-feed-inline-editor.in').first();
|
||||
if (preopenedEditor.length) {
|
||||
window.setTimeout(function() {
|
||||
scrollInlineEditorIntoView(preopenedEditor);
|
||||
}, 260);
|
||||
}
|
||||
|
||||
function renderMedicalPreview(source) {
|
||||
var preview = $('#medical-media-preview');
|
||||
if (!preview.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
preview.html('<img src="' + source + '" alt="Selected medical media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
} else {
|
||||
preview.html('<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
function markSelectedMedicalThumb(value) {
|
||||
$('.medical-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.medical-media-select-thumb').each(function() {
|
||||
if (($(this).attr('data-media-url') || '') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).off('click.medicalMediaSelect', pageRootSelector + ' .medical-media-select-thumb').on('click.medicalMediaSelect', pageRootSelector + ' .medical-media-select-thumb', function() {
|
||||
var mediaUrl = $(this).attr('data-media-url') || '';
|
||||
$('#medical-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMedicalThumb(mediaUrl);
|
||||
$('#medicalMediaUpload').val('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
renderMedicalPreview(mediaUrl);
|
||||
}
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
});
|
||||
|
||||
$(document).off('change.medicalMediaUpload', '#medicalMediaUpload').on('change.medicalMediaUpload', '#medicalMediaUpload', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#medical-media-gallery-path').val('');
|
||||
markSelectedMedicalThumb('');
|
||||
$('input[name="removeMedicalMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
renderMedicalPreview(e.target.result);
|
||||
$('#{$medicalGalleryModalId}').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$(document).off('shown.bs.modal.medicalMediaModal', '#{$medicalGalleryModalId}').on('shown.bs.modal.medicalMediaModal', '#{$medicalGalleryModalId}', function() {
|
||||
markSelectedMedicalThumb($('#medical-media-gallery-path').val());
|
||||
});
|
||||
|
||||
$(document).off('submit.medicalVisitAjax', formSelector).on('submit.medicalVisitAjax', formSelector, function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var form = this;
|
||||
var formData = new FormData(form);
|
||||
var submitButtons = $(form).find('button[type="submit"], input[type="submit"]');
|
||||
submitButtons.prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: form.action,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false
|
||||
}).always(function() {
|
||||
submitButtons.prop('disabled', false);
|
||||
}).done(function() {
|
||||
$('#{$medicalAddModalId}').modal('hide');
|
||||
refreshMedicalVisitsPageRoot();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.initMedicalVisitsPage();
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
694
views/animals/progress-updates.php
Normal file
694
views/animals/progress-updates.php
Normal file
@@ -0,0 +1,694 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\forms\AnimalProgressUpdateForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var bool $canManage */
|
||||
/* @var AnimalProgressUpdateForm $progressUpdateForm */
|
||||
/* @var AnimalProgressUpdate[] $progressUpdates */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
|
||||
$renderCustomField = static function (string $fieldKey, AnimalProgressUpdateForm $model, array $definitions): string {
|
||||
if (!isset($definitions[$fieldKey])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$definition = $definitions[$fieldKey];
|
||||
$inputType = (string)$definition['input_type'];
|
||||
$label = (string)$definition['label'];
|
||||
if ((int)$definition['required'] === 1) {
|
||||
$label .= ' *';
|
||||
}
|
||||
|
||||
$fieldName = "AnimalProgressUpdateForm[customFields][$fieldKey]";
|
||||
$fieldValue = $model->customFields[$fieldKey] ?? '';
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?php if ($inputType === 'textarea'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textarea($fieldName, (string)$fieldValue, ['class' => 'form-control', 'rows' => 3, 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'boolean'): ?>
|
||||
<div class="checkbox" style="margin-bottom:10px;">
|
||||
<label>
|
||||
<?= Html::hiddenInput($fieldName, '0') ?>
|
||||
<?= Html::checkbox($fieldName, !empty($fieldValue), ['value' => '1']) ?>
|
||||
<?= Html::encode($label) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'select'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::dropDownList(
|
||||
$fieldName,
|
||||
(string)$fieldValue,
|
||||
$model->getCustomFieldSelectOptions($fieldKey),
|
||||
['class' => 'form-control', 'prompt' => Yii::t('AnimalManagementModule.base', 'Select...'), 'id' => "animalprogressupdateform-customfields-$fieldKey"]
|
||||
) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'number'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('number', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'step' => 'any', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'date'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('date', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php elseif ($inputType === 'datetime'): ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::input('datetime-local', $fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="animalprogressupdateform-customfields-<?= Html::encode($fieldKey) ?>"><?= Html::encode($label) ?></label>
|
||||
<?= Html::textInput($fieldName, (string)$fieldValue, ['class' => 'form-control', 'id' => "animalprogressupdateform-customfields-$fieldKey"]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return (string)ob_get_clean();
|
||||
};
|
||||
|
||||
$progressCustomDefinitions = $progressUpdateForm->getCustomFieldDefinitions();
|
||||
$knownProgressKeys = ['progress_notes', 'routine_updates', 'media_reference'];
|
||||
$otherProgressCustomDefinitions = [];
|
||||
foreach ($progressCustomDefinitions as $fieldKey => $definition) {
|
||||
if (in_array($fieldKey, $knownProgressKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
$otherProgressCustomDefinitions[$fieldKey] = $definition;
|
||||
}
|
||||
|
||||
$currentMediaReference = trim((string)($progressUpdateForm->customFields['media_reference'] ?? ''));
|
||||
$progressAddModalId = 'add-progress-update-modal';
|
||||
$progressFormId = 'add-progress-update-main-form';
|
||||
$openProgressEditId = (int)Yii::$app->request->get('inlineProgressEdit', 0);
|
||||
$openProgressAdd = (int)Yii::$app->request->get('inlineProgressAdd', 0) === 1;
|
||||
?>
|
||||
|
||||
<style>
|
||||
.progress-feed-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #d5dfe8;
|
||||
border-radius: 12px;
|
||||
background: #223446;
|
||||
margin-bottom: 12px;
|
||||
min-height: 240px;
|
||||
box-shadow: 0 8px 22px rgba(12, 24, 36, 0.16);
|
||||
}
|
||||
|
||||
.progress-feed-cover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-feed-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(110deg, rgba(10, 18, 28, 0.28) 10%, rgba(10, 18, 28, 0.55) 52%, rgba(10, 18, 28, 0.75) 100%);
|
||||
}
|
||||
|
||||
.progress-feed-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 240px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.progress-feed-top-row {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-feed-date {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.progress-chip-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.progress-chip-row--top {
|
||||
margin-bottom: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.progress-chip {
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.progress-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
margin-left: auto;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
background: rgba(10, 18, 28, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
padding: 12px;
|
||||
color: #ecf2f8;
|
||||
}
|
||||
|
||||
.progress-feed-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-feed-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
color: rgba(231, 241, 249, 0.78);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.progress-feed-copy {
|
||||
color: #eff5fb;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.progress-feed-inline-editor {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
margin: 0 0 14px 0;
|
||||
padding-top: 0;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.progress-feed-inline-editor iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.progress-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.progress-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.progress-chip-row--top {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="progress-updates-page" class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;align-items:center;">
|
||||
<span style="font-size:20px;line-height:1.2;"><?= Yii::t('AnimalManagementModule.base', '<strong>Progress Feed</strong>') ?></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin-bottom:12px;">
|
||||
<?= Html::a(
|
||||
Html::encode($animal->getDisplayName()),
|
||||
$space->createUrl('/animal_management/animals/view', ['id' => $animal->id]),
|
||||
['style' => 'font-size:20px;font-weight:700;line-height:1.2;']
|
||||
) ?>
|
||||
<?= Html::a(
|
||||
Yii::t('AnimalManagementModule.base', 'All Animals'),
|
||||
$space->createUrl('/animal_management/animals/index'),
|
||||
['style' => 'font-size:16px;line-height:1.2;']
|
||||
) ?>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add Progress Update'), '#progress-add-inline', [
|
||||
'class' => 'btn btn-success btn-sm',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Add Progress Update'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Add Progress Update'),
|
||||
'data-toggle' => 'collapse',
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="progress-add-inline" class="collapse progress-feed-inline-editor<?= $openProgressAdd ? ' in' : '' ?>" style="margin:0 0 14px 0;">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/add-progress-update-inline', ['id' => $animal->id, 'inline' => 1, 'returnTo' => 'progress-updates', '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:860px;border:1px solid rgba(255,255,255,0.22);border-radius:10px;background:transparent;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($progressUpdates)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No progress updates recorded.') ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($progressUpdates as $update): ?>
|
||||
<?php
|
||||
$progressCustomValues = $update->getCustomFieldDisplayValues($canManage);
|
||||
$mediaReference = '';
|
||||
$progressCustomDisplayValues = [];
|
||||
foreach ($progressCustomValues as $customField) {
|
||||
if ((string)($customField['field_key'] ?? '') === 'media_reference') {
|
||||
$mediaReference = trim((string)$customField['value']);
|
||||
continue;
|
||||
}
|
||||
|
||||
$progressCustomDisplayValues[] = $customField;
|
||||
}
|
||||
|
||||
$hasMediaImage = $mediaReference !== '' && (preg_match('/^https?:\/\//i', $mediaReference) || substr($mediaReference, 0, 1) === '/');
|
||||
?>
|
||||
<div id="progress-update-<?= (int)$update->id ?>" class="progress-feed-card">
|
||||
<?php if ($hasMediaImage): ?>
|
||||
<img class="progress-feed-cover" src="<?= Html::encode($mediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Progress media') ?>">
|
||||
<?php endif; ?>
|
||||
<div class="progress-feed-overlay"></div>
|
||||
<div class="progress-feed-content">
|
||||
<div class="progress-feed-top-row">
|
||||
<span class="progress-feed-date"><?= Html::encode(DateDisplayHelper::format((string)$update->update_at)) ?></span>
|
||||
<?php if (!empty($update->weight) || !empty($update->vitals)): ?>
|
||||
<div class="progress-chip-row progress-chip-row--top">
|
||||
<?php if (!empty($update->weight)): ?><span class="progress-chip"><?= Yii::t('AnimalManagementModule.base', 'Weight') ?>: <?= Html::encode((string)$update->weight) ?></span><?php endif; ?>
|
||||
<?php if (!empty($update->vitals)): ?><span class="progress-chip"><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></span><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="progress-feed-details">
|
||||
<div class="progress-feed-header">
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a(
|
||||
'<i class="fa fa-pencil"></i>',
|
||||
'#progress-edit-inline-' . (int)$update->id,
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-toggle' => 'collapse',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($update->vitals)): ?><div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></div><div class="progress-feed-copy"><?= nl2br(Html::encode($update->vitals)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->behavior_notes)): ?><div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Behavior') ?></div><div class="progress-feed-copy"><?= nl2br(Html::encode($update->behavior_notes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->meal_plan_changes)): ?><div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Meal Plan') ?></div><div class="progress-feed-copy"><?= nl2br(Html::encode($update->meal_plan_changes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->housing_changes)): ?><div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Housing') ?></div><div class="progress-feed-copy"><?= nl2br(Html::encode($update->housing_changes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->medical_concerns)): ?><div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Medical Concerns') ?></div><div class="progress-feed-copy"><?= nl2br(Html::encode($update->medical_concerns)) ?></div><?php endif; ?>
|
||||
|
||||
<?php if (!empty($progressCustomDisplayValues)): ?>
|
||||
<div class="progress-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Additional Fields') ?></div>
|
||||
<div class="progress-feed-copy">
|
||||
<?php foreach ($progressCustomDisplayValues as $customField): ?>
|
||||
<div><strong><?= Html::encode((string)$customField['label']) ?>:</strong> <?= nl2br(Html::encode((string)$customField['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="progress-edit-inline-<?= (int)$update->id ?>" class="collapse progress-feed-inline-editor<?= $openProgressEditId === (int)$update->id ? ' in' : '' ?>">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/edit-progress-update', ['id' => $animal->id, 'updateId' => $update->id, 'inline' => 1, 'returnTo' => 'progress-updates', '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:760px;border:1px solid rgba(255,255,255,0.22);border-radius:10px;background:transparent;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div class="modal fade" id="<?= Html::encode($progressAddModalId) ?>" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Add Progress Update') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php $progressForm = ActiveForm::begin([
|
||||
'action' => $space->createUrl('/animal_management/animals/add-progress-update', ['id' => $animal->id]),
|
||||
'options' => ['id' => $progressFormId, 'enctype' => 'multipart/form-data'],
|
||||
]); ?>
|
||||
<?= Html::hiddenInput('returnTo', 'progress-updates') ?>
|
||||
<?= $progressForm->errorSummary($progressUpdateForm, ['showAllErrors' => true]) ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Progress Update') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><?= $progressForm->field($progressUpdateForm, 'weight') ?></div>
|
||||
<div class="col-sm-8"><?= $progressForm->field($progressUpdateForm, 'vitals')->textInput(['maxlength' => 255]) ?></div>
|
||||
<div class="col-sm-6"><?= $progressForm->field($progressUpdateForm, 'behavior_notes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $progressForm->field($progressUpdateForm, 'medical_concerns')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $progressForm->field($progressUpdateForm, 'meal_plan_changes')->textarea(['rows' => 2]) ?></div>
|
||||
<div class="col-sm-6"><?= $progressForm->field($progressUpdateForm, 'housing_changes')->textarea(['rows' => 2]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Notes') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $renderCustomField('progress_notes', $progressUpdateForm, $progressCustomDefinitions) ?>
|
||||
<?= $renderCustomField('routine_updates', $progressUpdateForm, $progressCustomDefinitions) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($otherProgressCustomDefinitions)): ?>
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?php foreach ($otherProgressCustomDefinitions as $fieldKey => $definition): ?>
|
||||
<?= $renderCustomField($fieldKey, $progressUpdateForm, $otherProgressCustomDefinitions) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<input type="hidden" id="progress-media-gallery-path" name="progressMediaGalleryPath" value="<?= Html::encode($currentMediaReference) ?>">
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="margin-bottom:8px;">
|
||||
<div id="progress-media-preview" style="border-radius:8px;overflow:hidden;background:#f2f4f6;height:150px;display:flex;align-items:center;justify-content:center;">
|
||||
<?php if ($currentMediaReference !== '' && (preg_match('/^https?:\/\//i', $currentMediaReference) || substr($currentMediaReference, 0, 1) === '/')): ?>
|
||||
<img src="<?= Html::encode($currentMediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Selected media') ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<i class="fa fa-image fa-2x" style="color:#a7b0b8;"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#progress-media-modal" style="margin-bottom:8px;">
|
||||
<i class="fa fa-photo"></i> <?= Yii::t('AnimalManagementModule.base', 'Choose from Gallery or Upload') ?>
|
||||
</button>
|
||||
<div class="checkbox" style="margin-top:0;">
|
||||
<label>
|
||||
<input type="checkbox" name="removeProgressMedia" value="1">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Remove selected media') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" style="margin-bottom:12px;">
|
||||
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Social Post') ?></strong></div>
|
||||
<div class="panel-body" style="padding-bottom:8px;">
|
||||
<?= $progressForm->field($progressUpdateForm, 'post_to_space_feed')->checkbox() ?>
|
||||
<?= $progressForm->field($progressUpdateForm, 'post_to_animal_feed')->checkbox() ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Add Progress Update'))->submit() ?>
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="progress-media-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Select Progress Media') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No gallery images available yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="max-height:280px;overflow:auto;margin-bottom:10px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-4" style="margin-bottom:8px;">
|
||||
<button type="button" class="btn btn-default progress-media-select-thumb" data-media-url="<?= Html::encode($galleryUrl) ?>" style="width:100%;padding:3px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:120px;object-fit:cover;border-radius:4px;">
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label class="control-label" for="progressMediaUpload"><?= Yii::t('AnimalManagementModule.base', 'Upload New Image') ?></label>
|
||||
<input type="file" class="form-control" id="progressMediaUpload" name="progressMediaUpload" form="<?= Html::encode($progressFormId) ?>" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerCss(<<<CSS
|
||||
.progress-media-select-thumb.is-selected {
|
||||
border-color: #2f7df4 !important;
|
||||
box-shadow: 0 0 0 2px rgba(47, 125, 244, 0.22);
|
||||
}
|
||||
CSS
|
||||
);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
(function() {
|
||||
var pageRootSelector = '#progress-updates-page';
|
||||
var formSelector = '#{$progressFormId}';
|
||||
|
||||
function scrollInlineEditorIntoView(editor) {
|
||||
var editorNode = $(editor);
|
||||
if (!editorNode.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sideSpacing = parseFloat(editorNode.css('margin-left'));
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = parseFloat(editorNode.closest('.panel-body').css('padding-left'));
|
||||
}
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = 14;
|
||||
}
|
||||
|
||||
var fixedHeaderHeight = 0;
|
||||
$('.navbar-fixed-top:visible, #topbar:visible, .topbar:visible, .layout-top-container:visible').each(function() {
|
||||
var h = $(this).outerHeight() || 0;
|
||||
if (h > fixedHeaderHeight) {
|
||||
fixedHeaderHeight = h;
|
||||
}
|
||||
});
|
||||
|
||||
var topReserve = Math.max(sideSpacing, 14) + Math.max(fixedHeaderHeight, 64) + 28;
|
||||
var top = Math.max(0, editorNode.offset().top - topReserve);
|
||||
$('html, body').stop(true).animate({scrollTop: top}, 220);
|
||||
}
|
||||
|
||||
function refreshProgressUpdatesPageRoot() {
|
||||
return $.get(window.location.href).done(function(html) {
|
||||
var doc = $('<div></div>').append($.parseHTML(html, document, true));
|
||||
var nextRoot = doc.find(pageRootSelector).first();
|
||||
if (!nextRoot.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(pageRootSelector).replaceWith(nextRoot);
|
||||
if (typeof window.initProgressUpdatesPage === 'function') {
|
||||
window.initProgressUpdatesPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.__animalProgressUpdatesInlineListenerBound) {
|
||||
window.__animalProgressUpdatesInlineListenerBound = true;
|
||||
window.addEventListener('message', function(event) {
|
||||
var data = event.data || {};
|
||||
if (!data || typeof data !== 'object' || data.source !== 'animal-inline-editor') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'cancel') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'saved') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
refreshProgressUpdatesPageRoot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.initProgressUpdatesPage = function() {
|
||||
$(document)
|
||||
.off('shown.bs.collapse.progressInlineScroll', pageRootSelector + ' .progress-feed-inline-editor')
|
||||
.on('shown.bs.collapse.progressInlineScroll', pageRootSelector + ' .progress-feed-inline-editor', function() {
|
||||
scrollInlineEditorIntoView(this);
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('click.progressInlineScroll', pageRootSelector + ' a[href^="#progress-edit-inline-"], ' + pageRootSelector + ' a[href="#progress-add-inline"]')
|
||||
.on('click.progressInlineScroll', pageRootSelector + ' a[href^="#progress-edit-inline-"], ' + pageRootSelector + ' a[href="#progress-add-inline"]', function() {
|
||||
var target = $(this).attr('href');
|
||||
if (!target || target.charAt(0) !== '#') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.setTimeout(function() {
|
||||
scrollInlineEditorIntoView($(target));
|
||||
}, 260);
|
||||
});
|
||||
|
||||
var preopenedEditor = $(pageRootSelector + ' .progress-feed-inline-editor.in').first();
|
||||
if (preopenedEditor.length) {
|
||||
window.setTimeout(function() {
|
||||
scrollInlineEditorIntoView(preopenedEditor);
|
||||
}, 260);
|
||||
}
|
||||
|
||||
function markSelectedMediaThumb(value) {
|
||||
$('.progress-media-select-thumb').removeClass('is-selected');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.progress-media-select-thumb').each(function() {
|
||||
if (($(this).attr('data-media-url') || '') === value) {
|
||||
$(this).addClass('is-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).off('click.progressMediaSelect', '.progress-media-select-thumb').on('click.progressMediaSelect', '.progress-media-select-thumb', function(event) {
|
||||
event.preventDefault();
|
||||
var mediaUrl = $(this).attr('data-media-url') || '';
|
||||
$('#progress-media-gallery-path').val(mediaUrl);
|
||||
markSelectedMediaThumb(mediaUrl);
|
||||
$('#progressMediaUpload').val('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
if (mediaUrl) {
|
||||
$('#progress-media-preview').html('<img src="' + mediaUrl + '" alt="Selected media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
}
|
||||
$('#progress-media-modal').modal('hide');
|
||||
});
|
||||
|
||||
$(document).off('change.progressMediaUpload', '#progressMediaUpload').on('change.progressMediaUpload', '#progressMediaUpload', function() {
|
||||
var file = this.files && this.files[0] ? this.files[0] : null;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#progress-media-gallery-path').val('');
|
||||
markSelectedMediaThumb('');
|
||||
$('input[name="removeProgressMedia"]').prop('checked', false);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('#progress-media-preview').html('<img src="' + e.target.result + '" alt="Selected media" style="width:100%;height:100%;object-fit:cover;">');
|
||||
$('#progress-media-modal').modal('hide');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$(document).off('shown.bs.modal.progressMediaModal', '#progress-media-modal').on('shown.bs.modal.progressMediaModal', '#progress-media-modal', function() {
|
||||
markSelectedMediaThumb($('#progress-media-gallery-path').val());
|
||||
});
|
||||
|
||||
$(document).off('show.bs.modal.progressMediaStack', '#progress-media-modal').on('show.bs.modal.progressMediaStack', '#progress-media-modal', function() {
|
||||
var zIndex = 1060 + (10 * $('.modal.in:visible').length);
|
||||
$(this).css('z-index', zIndex);
|
||||
|
||||
window.setTimeout(function() {
|
||||
$('.modal-backdrop').not('.progress-media-stack').last().css('z-index', zIndex - 1).addClass('progress-media-stack');
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$(document).off('hidden.bs.modal.progressMediaStack', '#progress-media-modal').on('hidden.bs.modal.progressMediaStack', '#progress-media-modal', function() {
|
||||
$(this).css('z-index', '');
|
||||
if ($('#{$progressAddModalId}').is(':visible')) {
|
||||
$('body').addClass('modal-open');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('submit.progressAjax', formSelector).on('submit.progressAjax', formSelector, function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var form = this;
|
||||
var formData = new FormData(form);
|
||||
var submitButtons = $(form).find('button[type="submit"], input[type="submit"]');
|
||||
submitButtons.prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: form.action,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false
|
||||
}).always(function() {
|
||||
submitButtons.prop('disabled', false);
|
||||
}).done(function() {
|
||||
$('#{$progressAddModalId}').modal('hide');
|
||||
refreshProgressUpdatesPageRoot();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.initProgressUpdatesPage();
|
||||
})();
|
||||
JS
|
||||
);
|
||||
?>
|
||||
31
views/animals/transfer.php
Normal file
31
views/animals/transfer.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\forms\TransferRequestForm;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var TransferRequestForm $model */
|
||||
?>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<?= Yii::t('AnimalManagementModule.base', '<strong>Transfer Request</strong> for {animal}', ['animal' => $animal->getDisplayName()]) ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php $form = ActiveForm::begin(); ?>
|
||||
|
||||
<?= $form->field($model, 'to_space_id')->dropDownList($model->getTargetOptions(), ['prompt' => Yii::t('AnimalManagementModule.base', 'Select destination rescue')]) ?>
|
||||
<?= $form->field($model, 'request_message')->textarea(['rows' => 4]) ?>
|
||||
<?= $form->field($model, 'conditions_text')->textarea(['rows' => 3]) ?>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Send Request'))->submit() ?>
|
||||
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
|
||||
->link($space->createUrl('/animal_management/animals/index')) ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
948
views/animals/view.php
Normal file
948
views/animals/view.php
Normal file
@@ -0,0 +1,948 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\AnimalTransfer;
|
||||
use humhub\modules\animal_management\models\AnimalTransferEvent;
|
||||
use humhub\modules\gallery\assets\Assets as GalleryAssets;
|
||||
use humhub\modules\space\models\Space;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Space $space */
|
||||
/* @var Animal $animal */
|
||||
/* @var bool $canManage */
|
||||
/* @var AnimalMedicalVisit[] $medicalVisits */
|
||||
/* @var AnimalProgressUpdate[] $progressUpdates */
|
||||
/* @var AnimalTransfer[] $transfers */
|
||||
/* @var AnimalTransferEvent[] $transferEvents */
|
||||
/* @var AnimalGalleryItem[] $galleryItems */
|
||||
/* @var array $customFieldValues */
|
||||
/* @var string $animalCoverImageUrl */
|
||||
/* @var array $detailHeroFields */
|
||||
|
||||
$openMedicalEditId = (int)Yii::$app->request->get('inlineMedicalEdit', 0);
|
||||
$openProgressEditId = (int)Yii::$app->request->get('inlineProgressEdit', 0);
|
||||
|
||||
$coverImageUrl = trim((string)$animalCoverImageUrl);
|
||||
$hasCoverImage = $coverImageUrl !== '' && (preg_match('/^https?:\/\//i', $coverImageUrl) || substr($coverImageUrl, 0, 1) === '/');
|
||||
$statusLabel = Animal::statusOptions()[$animal->status] ?? (string)$animal->status;
|
||||
|
||||
$detailFieldMap = [
|
||||
'name' => (string)$animal->getDisplayName(),
|
||||
'species' => (string)$animal->species,
|
||||
'breed' => (string)$animal->breed,
|
||||
'sex' => (string)$animal->sex,
|
||||
'status' => (string)$statusLabel,
|
||||
'location_name' => (string)$animal->location_name,
|
||||
'animal_uid' => (string)$animal->animal_uid,
|
||||
'public_summary' => trim((string)$animal->public_summary),
|
||||
'last_medical' => !empty($medicalVisits) ? DateDisplayHelper::format((string)$medicalVisits[0]->visit_at) : '',
|
||||
];
|
||||
|
||||
$heroFieldValues = [];
|
||||
foreach ($detailHeroFields as $fieldKey) {
|
||||
$fieldKey = trim((string)$fieldKey);
|
||||
if ($fieldKey === '' || $fieldKey === 'name' || !array_key_exists($fieldKey, $detailFieldMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = trim((string)$detailFieldMap[$fieldKey]);
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$heroFieldValues[] = $value;
|
||||
}
|
||||
|
||||
$customHeroCount = 0;
|
||||
foreach ($customFieldValues as $customField) {
|
||||
if ($customHeroCount >= 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
$label = trim((string)($customField['label'] ?? ''));
|
||||
$value = trim((string)($customField['value'] ?? ''));
|
||||
if ($label === '' || $value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$heroFieldValues[] = $label . ': ' . $value;
|
||||
$customHeroCount++;
|
||||
}
|
||||
|
||||
if (class_exists(GalleryAssets::class)) {
|
||||
GalleryAssets::register($this);
|
||||
}
|
||||
|
||||
$uiGalleryId = 'animal-gallery-' . (int)$animal->id;
|
||||
?>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div style="position:relative;min-height:320px;overflow:hidden;border-radius:12px;background:#dbe3eb;">
|
||||
<?php if ($hasCoverImage): ?>
|
||||
<img src="<?= Html::encode($coverImageUrl) ?>" alt="<?= Html::encode($animal->getDisplayName()) ?>" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#9aa5b1;">
|
||||
<i class="fa fa-paw fa-4x"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="position:absolute;inset:0;background:linear-gradient(180deg, rgba(7,10,16,0.08) 0%, rgba(7,10,16,0.6) 56%, rgba(7,10,16,0.84) 100%);"></div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div style="position:absolute;top:12px;right:12px;z-index:2;">
|
||||
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/edit', ['id' => $animal->id])) ?>" class="btn btn-default btn-sm" title="<?= Yii::t('AnimalManagementModule.base', 'Edit Profile') ?>" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Edit Profile') ?>" style="border-radius:999px;background:rgba(255,255,255,0.92);border:0;">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="position:absolute;left:14px;right:14px;bottom:14px;color:#fff;z-index:1;">
|
||||
<div style="font-size:30px;line-height:1.1;font-weight:800;margin-bottom:8px;text-shadow:0 3px 10px rgba(0,0,0,0.45);">
|
||||
<?= Html::encode($animal->getDisplayName()) ?>
|
||||
</div>
|
||||
<?php if (!empty($heroFieldValues)): ?>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px;">
|
||||
<?php foreach ($heroFieldValues as $heroFieldValue): ?>
|
||||
<span style="display:inline-block;background:rgba(15,23,42,0.58);border:1px solid rgba(255,255,255,0.28);padding:3px 10px;border-radius:999px;font-size:12px;">
|
||||
<?= Html::encode($heroFieldValue) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($animal->public_summary)): ?>
|
||||
<div style="font-size:13px;line-height:1.35;text-shadow:0 2px 8px rgba(0,0,0,0.45);max-width:960px;">
|
||||
<?= nl2br(Html::encode((string)$animal->public_summary)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->registerJs(<<<'JS'
|
||||
(function() {
|
||||
function getCsrfPayload() {
|
||||
var csrfParam = $('meta[name="csrf-param"]').attr('content') || '';
|
||||
var csrfToken = $('meta[name="csrf-token"]').attr('content') || '';
|
||||
var payload = {};
|
||||
if (csrfParam && csrfToken) {
|
||||
payload[csrfParam] = csrfToken;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function scrollInlineEditorIntoView(editor) {
|
||||
var $editor = $(editor);
|
||||
if (!$editor.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sideSpacing = parseFloat($editor.css('margin-left'));
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = parseFloat($editor.closest('.panel-body').css('padding-left'));
|
||||
}
|
||||
if (!(sideSpacing > 0)) {
|
||||
sideSpacing = 14;
|
||||
}
|
||||
|
||||
var fixedHeaderHeight = 0;
|
||||
$('.navbar-fixed-top:visible, #topbar:visible, .topbar:visible, .layout-top-container:visible').each(function() {
|
||||
var h = $(this).outerHeight() || 0;
|
||||
if (h > fixedHeaderHeight) {
|
||||
fixedHeaderHeight = h;
|
||||
}
|
||||
});
|
||||
|
||||
var topReserve = Math.max(sideSpacing, 14) + Math.max(fixedHeaderHeight, 64) + 28;
|
||||
|
||||
var top = Math.max(0, $editor.offset().top - topReserve);
|
||||
$('html, body').stop(true).animate({scrollTop: top}, 220);
|
||||
}
|
||||
|
||||
function refreshPanels(selectors) {
|
||||
selectors = selectors || [];
|
||||
if (!selectors.length) {
|
||||
return $.Deferred().resolve().promise();
|
||||
}
|
||||
|
||||
return $.get(window.location.href).done(function(html) {
|
||||
var $doc = $('<div></div>').append($.parseHTML(html, document, true));
|
||||
selectors.forEach(function(selector) {
|
||||
var $next = $doc.find(selector).first();
|
||||
if ($next.length) {
|
||||
$(selector).replaceWith($next);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('message', function(event) {
|
||||
var data = event.data || {};
|
||||
if (!data || typeof data !== 'object' || data.source !== 'animal-inline-editor') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'cancel') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'saved') {
|
||||
if (data.collapseId) {
|
||||
$('#' + data.collapseId).collapse('hide');
|
||||
}
|
||||
|
||||
var selectors = $.isArray(data.refreshSelectors) && data.refreshSelectors.length
|
||||
? data.refreshSelectors
|
||||
: ['#animal-medical-panel', '#animal-progress-panel', '#animal-gallery-panel'];
|
||||
|
||||
refreshPanels(selectors);
|
||||
}
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('shown.bs.collapse.animalInlineScroll', '.animal-inline-editor')
|
||||
.on('shown.bs.collapse.animalInlineScroll', '.animal-inline-editor', function() {
|
||||
scrollInlineEditorIntoView(this);
|
||||
});
|
||||
|
||||
$(document).on('submit', '#animal-gallery-upload', function(event) {
|
||||
event.preventDefault();
|
||||
var form = this;
|
||||
var filesInput = form.querySelector('#galleryImages');
|
||||
var selectedCount = filesInput && filesInput.files ? filesInput.files.length : 0;
|
||||
if (selectedCount > 10) {
|
||||
window.alert('You can upload up to 10 images at a time.');
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData(form);
|
||||
|
||||
$.ajax({
|
||||
url: form.action,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false
|
||||
}).done(function() {
|
||||
var shouldReopenModal = selectedCount > 0;
|
||||
if (form && typeof form.reset === 'function') {
|
||||
form.reset();
|
||||
}
|
||||
$('#animal-gallery-manage-modal').modal('hide');
|
||||
refreshPanels(['#animal-gallery-panel']).done(function() {
|
||||
if (shouldReopenModal) {
|
||||
$('#animal-gallery-manage-modal').modal('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.js-ajax-gallery-remove', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var $link = $(this);
|
||||
var confirmText = $link.data('confirmMessage') || $link.data('confirm');
|
||||
if (confirmText && !window.confirm(confirmText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.post($link.attr('href'), getCsrfPayload()).done(function() {
|
||||
refreshPanels(['#animal-gallery-panel']);
|
||||
});
|
||||
});
|
||||
})();
|
||||
JS
|
||||
, \yii\web\View::POS_END);
|
||||
?>
|
||||
|
||||
<div class="panel panel-default" id="animal-gallery-panel">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
|
||||
<span><?= Yii::t('AnimalManagementModule.base', '<strong>Gallery</strong>') ?></span>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add'), '#animal-gallery-manage-modal', [
|
||||
'class' => 'btn btn-xs btn-success',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Upload Gallery Images'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Upload Gallery Images'),
|
||||
'data-toggle' => 'modal',
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php if (empty($galleryItems)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'No gallery images yet.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="margin-bottom:8px;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-sm-4 col-md-3" style="margin-bottom:10px;">
|
||||
<div style="position:relative;border-radius:8px;overflow:hidden;background:#f3f5f7;">
|
||||
<a href="<?= Html::encode($galleryUrl) ?>#.jpeg"
|
||||
data-type="image"
|
||||
data-toggle="lightbox"
|
||||
data-parent="#animal-gallery-panel"
|
||||
data-ui-gallery="<?= Html::encode($uiGalleryId) ?>"
|
||||
data-pjax="0"
|
||||
data-pjax-prevent
|
||||
aria-label="<?= Yii::t('AnimalManagementModule.base', 'Open image') ?>">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:170px;object-fit:cover;display:block;">
|
||||
</a>
|
||||
<?php if ($canManage): ?>
|
||||
<div style="position:absolute;top:8px;right:8px;">
|
||||
<?= Html::a('<i class="fa fa-trash"></i>', $space->createUrl('/animal_management/animals/remove-gallery-image', ['id' => $animal->id, 'galleryId' => $galleryItem->id]), [
|
||||
'class' => 'btn btn-xs btn-default js-ajax-gallery-remove',
|
||||
'style' => 'background:rgba(255,255,255,0.92);border:0;',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Remove Image'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Remove Image'),
|
||||
'data-confirm-message' => Yii::t('AnimalManagementModule.base', 'Remove this image from the gallery?'),
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div class="modal fade" id="animal-gallery-manage-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?= Yii::t('AnimalManagementModule.base', 'Add Images to Gallery') ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (!empty($galleryItems)): ?>
|
||||
<div class="row" style="margin-bottom:10px;max-height:260px;overflow:auto;">
|
||||
<?php foreach ($galleryItems as $galleryItem): ?>
|
||||
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
|
||||
<?php if ($galleryUrl === '') { continue; } ?>
|
||||
<div class="col-xs-6 col-sm-3" style="margin-bottom:8px;">
|
||||
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" style="width:100%;height:110px;object-fit:cover;border-radius:6px;">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<hr style="margin-top:0;">
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="animal-gallery-upload" method="post" action="<?= Html::encode($space->createUrl('/animal_management/animals/add-gallery-images', ['id' => $animal->id])) ?>" enctype="multipart/form-data">
|
||||
<?= Html::hiddenInput(Yii::$app->request->csrfParam, Yii::$app->request->getCsrfToken()) ?>
|
||||
<div class="form-group" style="margin-bottom:8px;">
|
||||
<label class="control-label" for="galleryImages"><?= Yii::t('AnimalManagementModule.base', 'Upload Images') ?></label>
|
||||
<input type="file" class="form-control" id="galleryImages" name="galleryImages[]" accept="image/*" multiple>
|
||||
<div class="help-block" style="margin-bottom:0;"><?= Yii::t('AnimalManagementModule.base', 'You can upload up to 10 images at a time.') ?></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?= Yii::t('AnimalManagementModule.base', 'Upload to Gallery') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<style>
|
||||
.animal-feed-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #d5dfe8;
|
||||
border-radius: 12px;
|
||||
background: #223446;
|
||||
min-height: 240px;
|
||||
box-shadow: 0 8px 22px rgba(12, 24, 36, 0.16);
|
||||
}
|
||||
|
||||
.animal-feed-cover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.animal-feed-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(110deg, rgba(10, 18, 28, 0.28) 10%, rgba(10, 18, 28, 0.55) 52%, rgba(10, 18, 28, 0.75) 100%);
|
||||
}
|
||||
|
||||
.animal-feed-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 240px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.animal-feed-top-row {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.animal-feed-date {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.animal-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
margin-left: auto;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
background: rgba(10, 18, 28, 0.5);
|
||||
backdrop-filter: blur(2px);
|
||||
padding: 12px;
|
||||
color: #ecf2f8;
|
||||
}
|
||||
|
||||
.animal-feed-head {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.animal-feed-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.animal-feed-chips--top {
|
||||
margin-bottom: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.animal-feed-chip {
|
||||
display: inline-block;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.animal-feed-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
color: rgba(231, 241, 249, 0.78);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.animal-feed-copy {
|
||||
color: #eff5fb;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.animal-inline-editor {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
margin: 0 0 14px 0;
|
||||
padding-top: 0;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 12px;
|
||||
background: rgba(10, 18, 28, 0.2);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.animal-inline-editor iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.animal-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.animal-feed-details {
|
||||
width: 55%;
|
||||
max-width: 55%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.animal-feed-chips--top {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" id="animal-medical-panel">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
|
||||
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])) ?>" style="font-weight:bold;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Medical Visits') ?>
|
||||
</a>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add'), $space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id, 'inlineMedicalAdd' => 1]) . '#medical-add-inline', [
|
||||
'class' => 'btn btn-xs btn-success',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Add Medical Visit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Add Medical Visit'),
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php if (empty($medicalVisits)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No medical visits recorded.') ?></div>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$hiddenMedicalKeys = [
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
];
|
||||
|
||||
$knownMedicalKeys = [
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'medical_media_reference',
|
||||
'media_reference',
|
||||
];
|
||||
$medicalVitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
?>
|
||||
<?php foreach ($medicalVisits as $visit): ?>
|
||||
<?php
|
||||
$visitCustomValues = $visit->getCustomFieldDisplayValues($canManage);
|
||||
$visitFieldsByKey = [];
|
||||
$additionalVisitFields = [];
|
||||
$medicalMedia = '';
|
||||
foreach ($visitCustomValues as $customField) {
|
||||
$fieldKey = (string)($customField['field_key'] ?? '');
|
||||
if (in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldValue = trim((string)($customField['value'] ?? ''));
|
||||
if ($fieldValue === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fieldKey === 'medical_media_reference' || $fieldKey === 'media_reference') {
|
||||
$medicalMedia = $fieldValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true)) {
|
||||
$visitFieldsByKey[$fieldKey] = [
|
||||
'label' => (string)($medicalVitalLabelOverrides[$fieldKey] ?? ($customField['label'] ?? $fieldKey)),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$additionalVisitFields[] = [
|
||||
'label' => (string)($customField['label'] ?? $fieldKey),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
}
|
||||
|
||||
$hasMedicalMedia = $medicalMedia !== '' && (preg_match('/^https?:\/\//i', $medicalMedia) || substr($medicalMedia, 0, 1) === '/');
|
||||
$visitDateDisplay = DateDisplayHelper::format((string)$visit->visit_at);
|
||||
?>
|
||||
<div class="panel panel-default animal-feed-card" style="margin-bottom:12px;">
|
||||
<?php if ($hasMedicalMedia): ?>
|
||||
<img class="animal-feed-cover" src="<?= Html::encode($medicalMedia) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Medical media') ?>">
|
||||
<?php endif; ?>
|
||||
<div class="animal-feed-overlay"></div>
|
||||
<div class="animal-feed-content">
|
||||
<?php
|
||||
$vitalKeys = ['weight', 'pulse', 'blood_pressure', 'oxygen'];
|
||||
$hasVitals = false;
|
||||
foreach ($vitalKeys as $vitalKey) {
|
||||
if (!empty($visitFieldsByKey[$vitalKey]['value'])) {
|
||||
$hasVitals = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="animal-feed-top-row">
|
||||
<span class="animal-feed-date"><?= Html::encode($visitDateDisplay !== '' ? $visitDateDisplay : (string)$visit->visit_at) ?></span>
|
||||
<?php if ($hasVitals): ?>
|
||||
<div class="animal-feed-chips animal-feed-chips--top">
|
||||
<?php foreach ($vitalKeys as $vitalKey): ?>
|
||||
<?php if (empty($visitFieldsByKey[$vitalKey]['value'])) { continue; } ?>
|
||||
<span class="animal-feed-chip"><?= Html::encode($visitFieldsByKey[$vitalKey]['label']) ?>: <?= Html::encode($visitFieldsByKey[$vitalKey]['value']) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="animal-feed-details">
|
||||
<div class="animal-feed-head">
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a(
|
||||
'<i class="fa fa-pencil"></i>',
|
||||
'#medical-edit-inline-' . (int)$visit->id,
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-toggle' => 'collapse',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($visit->provider_name)): ?>
|
||||
<div class="animal-feed-copy" style="margin-bottom:8px;"><?= Yii::t('AnimalManagementModule.base', 'Provider') ?>: <?= Html::encode((string)$visit->provider_name) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($visit->notes)): ?>
|
||||
<div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Clinical Notes') ?></div>
|
||||
<div class="animal-feed-copy"><?= nl2br(Html::encode((string)$visit->notes)) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($visit->recommendations)): ?>
|
||||
<div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Recommendations') ?></div>
|
||||
<div class="animal-feed-copy"><?= nl2br(Html::encode((string)$visit->recommendations)) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$conditionKeys = ['chronic_conditions', 'acute_conditions', 'special_needs'];
|
||||
foreach ($conditionKeys as $conditionKey):
|
||||
if (empty($visitFieldsByKey[$conditionKey]['value'])) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="animal-feed-label"><?= Html::encode($visitFieldsByKey[$conditionKey]['label']) ?></div>
|
||||
<div class="animal-feed-copy"><?= nl2br(Html::encode($visitFieldsByKey[$conditionKey]['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (!empty($visitFieldsByKey['date_of_most_recent_medical_visit']['value'])): ?>
|
||||
<div class="animal-feed-label"><?= Html::encode($visitFieldsByKey['date_of_most_recent_medical_visit']['label']) ?></div>
|
||||
<div class="animal-feed-copy"><?= Html::encode(DateDisplayHelper::format((string)$visitFieldsByKey['date_of_most_recent_medical_visit']['value'])) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$contactKeys = [
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
];
|
||||
$hasContact = false;
|
||||
foreach ($contactKeys as $contactKey) {
|
||||
if (!empty($visitFieldsByKey[$contactKey]['value'])) {
|
||||
$hasContact = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($hasContact): ?>
|
||||
<?php
|
||||
$contactLines = [];
|
||||
if (!empty($visitFieldsByKey['physician_name']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_name']['value'];
|
||||
}
|
||||
if (!empty($visitFieldsByKey['physician_business_name']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_business_name']['value'];
|
||||
}
|
||||
if (!empty($visitFieldsByKey['physician_street_address']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_street_address']['value'];
|
||||
}
|
||||
|
||||
$cityStateZip = trim(implode(', ', array_filter([
|
||||
trim((string)($visitFieldsByKey['physician_city']['value'] ?? '')),
|
||||
trim((string)($visitFieldsByKey['physician_state']['value'] ?? '')),
|
||||
])));
|
||||
$zipValue = trim((string)($visitFieldsByKey['physician_zip']['value'] ?? ''));
|
||||
if ($zipValue !== '') {
|
||||
$cityStateZip = trim($cityStateZip . ($cityStateZip !== '' ? ' ' : '') . $zipValue);
|
||||
}
|
||||
if ($cityStateZip !== '') {
|
||||
$contactLines[] = $cityStateZip;
|
||||
}
|
||||
|
||||
$phones = array_filter([
|
||||
trim((string)($visitFieldsByKey['physician_cell_phone']['value'] ?? '')),
|
||||
trim((string)($visitFieldsByKey['physician_business_phone']['value'] ?? '')),
|
||||
]);
|
||||
if (!empty($phones)) {
|
||||
$contactLines[] = implode(' · ', $phones);
|
||||
}
|
||||
|
||||
if (!empty($visitFieldsByKey['physician_license_number']['value'])) {
|
||||
$contactLines[] = (string)$visitFieldsByKey['physician_license_number']['value'];
|
||||
}
|
||||
?>
|
||||
<div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Care Contact') ?></div>
|
||||
<div class="animal-feed-copy">
|
||||
<?php foreach ($contactLines as $contactLine): ?>
|
||||
<div><?= Html::encode($contactLine) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($additionalVisitFields)): ?>
|
||||
<div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Additional Fields') ?></div>
|
||||
<div class="animal-feed-copy">
|
||||
<?php foreach ($additionalVisitFields as $additionalField): ?>
|
||||
<div><strong><?= Html::encode((string)$additionalField['label']) ?>:</strong> <?= nl2br(Html::encode((string)$additionalField['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="medical-edit-inline-<?= (int)$visit->id ?>" class="collapse animal-inline-editor<?= $openMedicalEditId === (int)$visit->id ? ' in' : '' ?>">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/edit-medical-visit', ['id' => $animal->id, 'visitId' => $visit->id, 'inline' => 1, '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:640px;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" id="animal-progress-panel">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
|
||||
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])) ?>" style="font-weight:bold;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Progress Feed') ?>
|
||||
</a>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add'), $space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id, 'inlineProgressAdd' => 1]) . '#progress-add-inline', [
|
||||
'class' => 'btn btn-xs btn-success',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Add Progress Update'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Add Progress Update'),
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php if (empty($progressUpdates)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;"><?= Yii::t('AnimalManagementModule.base', 'No progress updates recorded.') ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($progressUpdates as $update): ?>
|
||||
<?php
|
||||
$progressCustomValues = $update->getCustomFieldDisplayValues($canManage);
|
||||
$mediaReference = '';
|
||||
$progressCustomDisplayValues = [];
|
||||
foreach ($progressCustomValues as $customField) {
|
||||
if ((string)($customField['field_key'] ?? '') === 'media_reference') {
|
||||
$mediaReference = trim((string)$customField['value']);
|
||||
continue;
|
||||
}
|
||||
|
||||
$progressCustomDisplayValues[] = $customField;
|
||||
}
|
||||
|
||||
$hasMediaImage = $mediaReference !== '' && (preg_match('/^https?:\/\//i', $mediaReference) || substr($mediaReference, 0, 1) === '/');
|
||||
?>
|
||||
<div class="panel panel-default animal-feed-card" style="margin-bottom:12px;">
|
||||
<?php if ($hasMediaImage): ?>
|
||||
<img class="animal-feed-cover" src="<?= Html::encode($mediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Progress media') ?>">
|
||||
<?php endif; ?>
|
||||
<div class="animal-feed-overlay"></div>
|
||||
<div class="animal-feed-content">
|
||||
<div class="animal-feed-top-row">
|
||||
<span class="animal-feed-date"><?= Html::encode(DateDisplayHelper::format((string)$update->update_at)) ?></span>
|
||||
<?php if (!empty($update->weight) || !empty($update->vitals)): ?>
|
||||
<div class="animal-feed-chips animal-feed-chips--top">
|
||||
<?php if (!empty($update->weight)): ?><span class="animal-feed-chip"><?= Yii::t('AnimalManagementModule.base', 'Weight') ?>: <?= Html::encode((string)$update->weight) ?></span><?php endif; ?>
|
||||
<?php if (!empty($update->vitals)): ?><span class="animal-feed-chip"><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></span><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="animal-feed-details">
|
||||
<div class="animal-feed-head">
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a(
|
||||
'<i class="fa fa-pencil"></i>',
|
||||
'#progress-edit-inline-' . (int)$update->id,
|
||||
[
|
||||
'class' => 'btn btn-xs btn-default',
|
||||
'data-toggle' => 'collapse',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Edit'),
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($update->vitals)): ?><div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></div><div class="animal-feed-copy"><?= nl2br(Html::encode((string)$update->vitals)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->behavior_notes)): ?><div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Behavior') ?></div><div class="animal-feed-copy"><?= nl2br(Html::encode((string)$update->behavior_notes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->meal_plan_changes)): ?><div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Meal Plan') ?></div><div class="animal-feed-copy"><?= nl2br(Html::encode((string)$update->meal_plan_changes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->housing_changes)): ?><div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Housing') ?></div><div class="animal-feed-copy"><?= nl2br(Html::encode((string)$update->housing_changes)) ?></div><?php endif; ?>
|
||||
<?php if (!empty($update->medical_concerns)): ?><div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Medical Concerns') ?></div><div class="animal-feed-copy"><?= nl2br(Html::encode((string)$update->medical_concerns)) ?></div><?php endif; ?>
|
||||
|
||||
<?php if (!empty($progressCustomDisplayValues)): ?>
|
||||
<div class="animal-feed-label"><?= Yii::t('AnimalManagementModule.base', 'Additional Fields') ?></div>
|
||||
<div class="animal-feed-copy">
|
||||
<?php foreach ($progressCustomDisplayValues as $customField): ?>
|
||||
<div><strong><?= Html::encode((string)$customField['label']) ?>:</strong> <?= nl2br(Html::encode((string)$customField['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($canManage): ?>
|
||||
<div id="progress-edit-inline-<?= (int)$update->id ?>" class="collapse animal-inline-editor<?= $openProgressEditId === (int)$update->id ? ' in' : '' ?>">
|
||||
<iframe
|
||||
src="<?= Html::encode($space->createUrl('/animal_management/animals/edit-progress-update', ['id' => $animal->id, 'updateId' => $update->id, 'inline' => 1, '_v' => time()])) ?>"
|
||||
style="width:100%;min-height:760px;"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
|
||||
<span><?= Yii::t('AnimalManagementModule.base', '<strong>Transfer Timeline</strong>') ?></span>
|
||||
<?php if ($canManage): ?>
|
||||
<?= Html::a('<i class="fa fa-plus"></i> ' . Yii::t('AnimalManagementModule.base', 'Add'), $space->createUrl('/animal_management/animals/transfer', ['id' => $animal->id]), [
|
||||
'class' => 'btn btn-xs btn-success',
|
||||
'title' => Yii::t('AnimalManagementModule.base', 'Request Transfer'),
|
||||
'aria-label' => Yii::t('AnimalManagementModule.base', 'Request Transfer'),
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?php if (empty($transfers)): ?>
|
||||
<div class="text-muted"><?= Yii::t('AnimalManagementModule.base', 'No transfers yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<table class="table table-condensed table-hover" style="margin-bottom:0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'From') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'To') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Status') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Updated') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transfers as $transfer): ?>
|
||||
<?php $fromSpace = $transfer->getFromSpace(); $toSpace = $transfer->getToSpace(); ?>
|
||||
<tr>
|
||||
<td><?= Html::encode($fromSpace ? $fromSpace->name : Yii::t('AnimalManagementModule.base', 'Unknown')) ?></td>
|
||||
<td><?= Html::encode($toSpace ? $toSpace->name : Yii::t('AnimalManagementModule.base', 'Unknown')) ?></td>
|
||||
<td><?= Html::encode(AnimalTransfer::statusOptions()[$transfer->status] ?? $transfer->status) ?></td>
|
||||
<td><?= Html::encode(DateDisplayHelper::format((string)$transfer->updated_at)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><?= Yii::t('AnimalManagementModule.base', '<strong>Transfer Audit Log</strong>') ?></div>
|
||||
<div class="panel-body">
|
||||
<?php if (empty($transferEvents)): ?>
|
||||
<div class="text-muted"><?= Yii::t('AnimalManagementModule.base', 'No transfer events recorded yet.') ?></div>
|
||||
<?php else: ?>
|
||||
<table class="table table-condensed table-hover" style="margin-bottom:0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'When') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'By') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Event') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Status Change') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Message') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transferEvents as $event): ?>
|
||||
<?php $actor = $event->createdByUser; ?>
|
||||
<tr>
|
||||
<td><?= Html::encode(DateDisplayHelper::format((string)$event->created_at)) ?></td>
|
||||
<td><?= Html::encode($actor ? $actor->displayName : Yii::t('AnimalManagementModule.base', 'System')) ?></td>
|
||||
<td><?= Html::encode((string)$event->event_type) ?></td>
|
||||
<td><?= Html::encode((string)($event->from_status ?: '-') . ' -> ' . (string)($event->to_status ?: '-')) ?></td>
|
||||
<td><?= Html::encode((string)$event->message) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
104
views/gallery/animal_gallery_view.php
Normal file
104
views/gallery/animal_gallery_view.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\gallery\assets\Assets as GalleryAssets;
|
||||
use humhub\modules\gallery\helpers\Url;
|
||||
use humhub\modules\gallery\models\CustomGallery;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\widgets\Button;
|
||||
|
||||
/* @var CustomGallery $gallery */
|
||||
/* @var Animal|null $animal */
|
||||
/* @var AnimalGalleryItem[] $items */
|
||||
/* @var Space $container */
|
||||
/* @var bool|string $showMore */
|
||||
|
||||
GalleryAssets::register($this);
|
||||
|
||||
$animalName = $animal instanceof Animal ? $animal->getDisplayName() : Yii::t('AnimalManagementModule.base', 'Animal');
|
||||
$backUrl = Url::toGalleryOverview($container);
|
||||
$animalUrl = $animal instanceof Animal
|
||||
? $container->createUrl('/animal_management/animals/view', ['id' => (int)$animal->id])
|
||||
: null;
|
||||
$uiGalleryId = 'animal-gallery-native-' . (int)$gallery->id;
|
||||
$descriptionText = preg_replace('/\s*\[animal-gallery:\d+\]\s*/', ' ', (string)$gallery->description);
|
||||
$descriptionText = trim((string)$descriptionText);
|
||||
$headerTitle = trim((string)$gallery->title);
|
||||
$headerTitle = preg_replace('/\s+Gallery\s*$/i', '', $headerTitle);
|
||||
if ($headerTitle === '') {
|
||||
$headerTitle = $animalName;
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="gallery-container" class="panel panel-default">
|
||||
<div class="panel-heading clearfix" style="background-color: <?= $this->theme->variable('background-color-secondary') ?>;">
|
||||
<div style="margin-right:40px;" class="pull-left">
|
||||
<?= Yii::t('GalleryModule.base', '<strong>Gallery</strong> ') . Html::encode($headerTitle) ?>
|
||||
</div>
|
||||
|
||||
<?= Button::back($backUrl, Yii::t('GalleryModule.base', 'Back to overview'))->right()->sm() ?>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<?php if ($animalUrl !== null): ?>
|
||||
<div style="margin-bottom:10px;">
|
||||
<?= Html::a(Html::encode($animalName), $animalUrl, ['style' => 'font-size:20px;font-weight:700;line-height:1.2;']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($descriptionText !== ''): ?>
|
||||
<div class="row clearfix" style="padding-bottom:8px;">
|
||||
<div class="col-sm-12 gallery-description">
|
||||
<i class="fa fa-arrow-circle-right"></i>
|
||||
<?= Html::encode($descriptionText) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="gallery-list" class="col">
|
||||
<div id="gallery-media-container" class="row">
|
||||
<?php if (empty($items)): ?>
|
||||
<div class="col-sm-12 text-muted" style="margin-bottom:10px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'No gallery images yet.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php
|
||||
if (!$item instanceof AnimalGalleryItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imageUrl = trim((string)$item->getImageUrl());
|
||||
if ($imageUrl === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = trim((string)$item->caption);
|
||||
$altText = $title !== '' ? $title : $animalName;
|
||||
?>
|
||||
<div class="col-sm-6 col-md-4 gallery-list-entry" style="margin-bottom:12px;">
|
||||
<a href="<?= Html::encode($imageUrl) ?>#.jpeg"
|
||||
data-type="image"
|
||||
data-toggle="lightbox"
|
||||
data-parent="#gallery-content"
|
||||
data-ui-gallery="<?= Html::encode($uiGalleryId) ?>"
|
||||
data-description="<?= Html::encode($title) ?>"
|
||||
title="<?= Html::encode($title) ?>"
|
||||
style="display:block;position:relative;aspect-ratio:4 / 3;background:#d8dee8;border-radius:12px;overflow:hidden;box-shadow:0 8px 24px rgba(15,23,42,0.14);">
|
||||
<img class="gallery-img" src="<?= Html::encode($imageUrl) ?>" alt="<?= Html::encode($altText) ?>" style="display:none;position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"/>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($showMore): ?>
|
||||
<div style="text-align:center;">
|
||||
<?= Button::primary(Yii::t('GalleryModule.base', 'Show more'))->action('gallery.showMore', (string)$showMore) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
168
views/settings/index.php
Normal file
168
views/settings/index.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
|
||||
use humhub\modules\animal_management\models\forms\FieldDefinitionSettingsForm;
|
||||
use humhub\widgets\Button;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var string|null $subNav */
|
||||
/* @var int $animalCount */
|
||||
/* @var FieldDefinitionSettingsForm $fieldSettingsForm */
|
||||
/* @var DisplaySettingsForm $displaySettingsForm */
|
||||
?>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><?= Yii::t('AnimalManagementModule.base', '<strong>Animal Management</strong> Settings') ?></div>
|
||||
|
||||
<?php if (!empty($subNav)): ?>
|
||||
<?= $subNav ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel-body">
|
||||
<p><?= Yii::t('AnimalManagementModule.base', 'Configure intake/profile field definitions used by Animal Management.') ?></p>
|
||||
|
||||
<div class="well well-sm" style="margin-bottom:12px;">
|
||||
<strong><?= Yii::t('AnimalManagementModule.base', 'Current animals in this space') ?>:</strong>
|
||||
<?= (int)$animalCount ?>
|
||||
</div>
|
||||
|
||||
<h4 style="margin-top:0;"><?= Yii::t('AnimalManagementModule.base', 'Display Settings') ?></h4>
|
||||
<?php $displayForm = ActiveForm::begin(); ?>
|
||||
<?= $displayForm->errorSummary($displaySettingsForm, ['showAllErrors' => true]) ?>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<?= $displayForm->field($displaySettingsForm, 'search_block_heading')->textInput(['maxlength' => 190]) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label class="control-label" style="display:block;"><?= Yii::t('AnimalManagementModule.base', 'Animal Tile Fields') ?></label>
|
||||
<?= Html::checkboxList(
|
||||
'DisplaySettingsForm[tile_fields]',
|
||||
$displaySettingsForm->tile_fields,
|
||||
DisplaySettingsForm::fieldOptions(),
|
||||
['separator' => '<br>']
|
||||
) ?>
|
||||
<p class="help-block" style="margin-top:6px;"><?= Yii::t('AnimalManagementModule.base', 'ID is hidden by default unless selected here.') ?></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="control-label" style="display:block;"><?= Yii::t('AnimalManagementModule.base', 'Animal Detail Hero Fields') ?></label>
|
||||
<?= Html::checkboxList(
|
||||
'DisplaySettingsForm[detail_fields]',
|
||||
$displaySettingsForm->detail_fields,
|
||||
DisplaySettingsForm::fieldOptions(),
|
||||
['separator' => '<br>']
|
||||
) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Display Settings'))->submit() ?>
|
||||
</div>
|
||||
<?php ActiveForm::end(); ?>
|
||||
|
||||
<?php if (!$fieldSettingsForm->canUseFieldDefinition()): ?>
|
||||
<div class="alert alert-warning" style="margin-bottom:0;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Field definition storage is unavailable. Enable rescue foundation migrations first.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php $form = ActiveForm::begin(); ?>
|
||||
|
||||
<?php if ($fieldSettingsForm->hasErrors('rows')): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= Html::errorSummary($fieldSettingsForm, ['header' => '', 'footer' => '', 'showAllErrors' => true]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 style="margin-top:0;"><?= Yii::t('AnimalManagementModule.base', 'Configured Fields') ?></h4>
|
||||
|
||||
<?php if (empty($fieldSettingsForm->rows)): ?>
|
||||
<div class="alert alert-info"><?= Yii::t('AnimalManagementModule.base', 'No field definitions found for this module.') ?></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive" style="margin-bottom:12px;">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Field Key') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Label') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Group') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Input Type') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Required') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Active') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Visibility') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Sort') ?></th>
|
||||
<th><?= Yii::t('AnimalManagementModule.base', 'Remove') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($fieldSettingsForm->rows as $index => $row): ?>
|
||||
<?php $isCore = ((int)($row['is_core'] ?? 0)) === 1; ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= Html::hiddenInput("FieldDefinitionSettingsForm[rows][$index][id]", (int)$row['id']) ?>
|
||||
<?= Html::hiddenInput("FieldDefinitionSettingsForm[rows][$index][field_key]", (string)$row['field_key']) ?>
|
||||
<?= Html::encode((string)$row['field_key']) ?>
|
||||
<?php if ($isCore): ?>
|
||||
<span class="label label-default" style="margin-left:4px;"><?= Yii::t('AnimalManagementModule.base', 'Core') ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= Html::textInput("FieldDefinitionSettingsForm[rows][$index][label]", (string)$row['label'], ['class' => 'form-control input-sm']) ?></td>
|
||||
<td><?= Html::textInput("FieldDefinitionSettingsForm[rows][$index][group_key]", (string)$row['group_key'], ['class' => 'form-control input-sm']) ?></td>
|
||||
<td>
|
||||
<?= Html::encode((string)$row['input_type']) ?>
|
||||
<?= Html::hiddenInput("FieldDefinitionSettingsForm[rows][$index][input_type]", (string)$row['input_type']) ?>
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align:middle;">
|
||||
<?= Html::checkbox("FieldDefinitionSettingsForm[rows][$index][required]", !empty($row['required']), ['disabled' => $isCore]) ?>
|
||||
<?php if ($isCore): ?>
|
||||
<?= Html::hiddenInput("FieldDefinitionSettingsForm[rows][$index][required]", !empty($row['required']) ? 1 : 0) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align:middle;">
|
||||
<?= Html::checkbox("FieldDefinitionSettingsForm[rows][$index][is_active]", !empty($row['is_active']), ['disabled' => $isCore]) ?>
|
||||
<?php if ($isCore): ?>
|
||||
<?= Html::hiddenInput("FieldDefinitionSettingsForm[rows][$index][is_active]", !empty($row['is_active']) ? 1 : 0) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?= Html::dropDownList(
|
||||
"FieldDefinitionSettingsForm[rows][$index][visibility]",
|
||||
(string)$row['visibility'],
|
||||
FieldDefinitionSettingsForm::visibilityOptions(),
|
||||
['class' => 'form-control input-sm']
|
||||
) ?>
|
||||
</td>
|
||||
<td><?= Html::input('number', "FieldDefinitionSettingsForm[rows][$index][sort_order]", (int)$row['sort_order'], ['class' => 'form-control input-sm', 'style' => 'max-width:90px;']) ?></td>
|
||||
<td class="text-center" style="vertical-align:middle;">
|
||||
<?php if (!$isCore): ?>
|
||||
<?= Html::checkbox("FieldDefinitionSettingsForm[rows][$index][remove]", false) ?>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4><?= Yii::t('AnimalManagementModule.base', 'Add Custom Field') ?></h4>
|
||||
<div class="row">
|
||||
<div class="col-md-3"><?= $form->field($fieldSettingsForm, 'new_field_key')->textInput(['placeholder' => 'e.g. coat_color']) ?></div>
|
||||
<div class="col-md-3"><?= $form->field($fieldSettingsForm, 'new_label') ?></div>
|
||||
<div class="col-md-2"><?= $form->field($fieldSettingsForm, 'new_input_type')->dropDownList(FieldDefinitionSettingsForm::inputTypeOptions()) ?></div>
|
||||
<div class="col-md-2"><?= $form->field($fieldSettingsForm, 'new_group_key') ?></div>
|
||||
<div class="col-md-2"><?= $form->field($fieldSettingsForm, 'new_sort_order')->input('number') ?></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3"><?= $form->field($fieldSettingsForm, 'new_visibility')->dropDownList(FieldDefinitionSettingsForm::visibilityOptions()) ?></div>
|
||||
<div class="col-md-3"><?= $form->field($fieldSettingsForm, 'new_required')->checkbox() ?></div>
|
||||
<div class="col-md-6"><?= $form->field($fieldSettingsForm, 'new_options')->textInput(['placeholder' => 'Optional JSON options']) ?></div>
|
||||
</div>
|
||||
|
||||
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Save Field Settings'))->submit() ?>
|
||||
<?php ActiveForm::end(); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
42
views/widgets/searchAnimalProfilesBlock.php
Normal file
42
views/widgets/searchAnimalProfilesBlock.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Animal[] $animals */
|
||||
/* @var \humhub\modules\content\components\ContentContainerActiveRecord $contentContainer */
|
||||
/* @var string $queryValue */
|
||||
?>
|
||||
|
||||
<form method="get" action="<?= Html::encode($contentContainer->createUrl('/animal_management/animals/index')) ?>" style="margin-bottom:10px;">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="q" value="<?= Html::encode($queryValue) ?>" placeholder="<?= Yii::t('AnimalManagementModule.base', 'Search by name, species, or ID') ?>">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="submit"><?= Yii::t('AnimalManagementModule.base', 'Search') ?></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (empty($animals)): ?>
|
||||
<div class="text-muted" style="margin-bottom:8px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'No animals found yet.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<ul class="list-unstyled" style="margin-bottom:10px;">
|
||||
<?php foreach ($animals as $animal): ?>
|
||||
<li style="padding:6px 0;border-bottom:1px solid #f0f0f0;">
|
||||
<strong><?= Html::encode($animal->getDisplayName()) ?></strong>
|
||||
<div class="text-muted" style="font-size:12px;">
|
||||
<?= Html::encode($animal->animal_uid) ?>
|
||||
<?php if (!empty($animal->species)): ?>
|
||||
· <?= Html::encode($animal->species) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<a class="btn btn-primary btn-sm" href="<?= Html::encode($contentContainer->createUrl('/animal_management/animals/index')) ?>">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Open Animals') ?>
|
||||
</a>
|
||||
306
widgets/SearchAnimalProfilesBlock.php
Normal file
306
widgets/SearchAnimalProfilesBlock.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\widgets;
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalFieldValue;
|
||||
use humhub\modules\animal_management\models\AnimalGalleryItem;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
|
||||
use Yii;
|
||||
use yii\base\Widget;
|
||||
|
||||
class SearchAnimalProfilesBlock extends Widget
|
||||
{
|
||||
public ContentContainerActiveRecord $contentContainer;
|
||||
public int $limit = 10;
|
||||
|
||||
public function run()
|
||||
{
|
||||
if (!$this->contentContainer->moduleManager->isEnabled('animal_management')) {
|
||||
return '<div class="well well-sm" style="margin-bottom:0;">'
|
||||
. Yii::t('AnimalManagementModule.base', 'Animal profiles are not enabled for this rescue.')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$queryValue = trim((string)Yii::$app->request->get('q', ''));
|
||||
$showAll = ((int)Yii::$app->request->get('animalFeedAll', 0)) === 1;
|
||||
$countParam = (int)Yii::$app->request->get('animalFeedCount', $this->limit);
|
||||
if ($countParam < $this->limit) {
|
||||
$countParam = $this->limit;
|
||||
}
|
||||
if ($countParam > 200) {
|
||||
$countParam = 200;
|
||||
}
|
||||
|
||||
$query = Animal::find()->where(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
|
||||
|
||||
if ($queryValue !== '') {
|
||||
$query->andWhere([
|
||||
'or',
|
||||
['like', 'animal_uid', $queryValue],
|
||||
['like', 'name', $queryValue],
|
||||
['like', 'species', $queryValue],
|
||||
]);
|
||||
}
|
||||
|
||||
$totalCount = (int)$query->count();
|
||||
$displayCount = $showAll ? $totalCount : min($countParam, $totalCount);
|
||||
|
||||
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
|
||||
$heading = trim((string)$settings->get('searchBlockHeading', DisplaySettingsForm::DEFAULT_SEARCH_BLOCK_HEADING));
|
||||
if ($heading === '') {
|
||||
$heading = DisplaySettingsForm::DEFAULT_SEARCH_BLOCK_HEADING;
|
||||
}
|
||||
|
||||
$tileFieldsRaw = $settings->get('tileFields', json_encode(DisplaySettingsForm::DEFAULT_TILE_FIELDS));
|
||||
$tileFields = $this->normalizeDisplayFields($tileFieldsRaw, DisplaySettingsForm::DEFAULT_TILE_FIELDS);
|
||||
|
||||
$animals = $query
|
||||
->orderBy(['updated_at' => SORT_DESC, 'id' => SORT_DESC])
|
||||
->limit($displayCount)
|
||||
->all();
|
||||
|
||||
$animalIds = array_map(static function (Animal $animal): int {
|
||||
return (int)$animal->id;
|
||||
}, $animals);
|
||||
|
||||
$latestMedicalVisitByAnimal = $this->resolveLatestMedicalVisits($animalIds);
|
||||
$animalImageUrls = $this->resolveAnimalImageUrls($animalIds, ['profile_image_url', 'profile_image', 'photo_url', 'image_url', 'photo'], false);
|
||||
$tileFieldOverrides = $this->resolveDisplayFieldOverrides($animalIds, 'tile_display_fields');
|
||||
|
||||
$hasMore = !$showAll && $displayCount < $totalCount;
|
||||
$nextCount = min($displayCount + $this->limit, $totalCount);
|
||||
|
||||
$viewFile = $this->getViewPath() . '/searchAnimalProfilesBlock.php';
|
||||
if (!is_file($viewFile)) {
|
||||
return '<div class="well well-sm" style="margin-bottom:0;">'
|
||||
. Yii::t('AnimalManagementModule.base', 'Animal search is temporarily unavailable.')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
return $this->render('searchAnimalProfilesBlock', [
|
||||
'animals' => $animals,
|
||||
'contentContainer' => $this->contentContainer,
|
||||
'queryValue' => $queryValue,
|
||||
'heading' => $heading,
|
||||
'tileFields' => $tileFields,
|
||||
'tileFieldOverrides' => $tileFieldOverrides,
|
||||
'latestMedicalVisitByAnimal' => $latestMedicalVisitByAnimal,
|
||||
'animalImageUrls' => $animalImageUrls,
|
||||
'totalCount' => $totalCount,
|
||||
'displayCount' => $displayCount,
|
||||
'hasMore' => $hasMore,
|
||||
'nextCount' => $nextCount,
|
||||
'showAll' => $showAll,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeDisplayFields($raw, array $default): array
|
||||
{
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
return $default;
|
||||
}
|
||||
$raw = $decoded;
|
||||
}
|
||||
|
||||
if (!is_array($raw)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$allowed = array_keys(DisplaySettingsForm::fieldOptions());
|
||||
$normalized = [];
|
||||
foreach ($raw as $field) {
|
||||
$field = trim((string)$field);
|
||||
if ($field === '' || !in_array($field, $allowed, true)) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($field, $normalized, true)) {
|
||||
$normalized[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($normalized) ? $normalized : $default;
|
||||
}
|
||||
|
||||
private function resolveLatestMedicalVisits(array $animalIds): array
|
||||
{
|
||||
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
|
||||
if (empty($animalIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$visits = AnimalMedicalVisit::find()
|
||||
->where(['animal_id' => $animalIds])
|
||||
->orderBy(['animal_id' => SORT_ASC, 'visit_at' => SORT_DESC, 'id' => SORT_DESC])
|
||||
->all();
|
||||
|
||||
$result = [];
|
||||
foreach ($visits as $visit) {
|
||||
$animalId = (int)$visit->animal_id;
|
||||
if (!isset($result[$animalId])) {
|
||||
$result[$animalId] = $visit;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function resolveAnimalImageUrls(array $animalIds, array $imageFieldOrder = [], bool $allowGalleryFallback = false): array
|
||||
{
|
||||
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
|
||||
if (empty($animalIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!class_exists(RescueFieldDefinition::class)
|
||||
|| Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
||||
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (empty($imageFieldOrder)) {
|
||||
$imageFieldOrder = ['cover_image_url', 'profile_image_url', 'photo_url', 'image_url', 'profile_image', 'photo'];
|
||||
}
|
||||
|
||||
$definitions = RescueFieldDefinition::find()
|
||||
->select(['id', 'field_key'])
|
||||
->where([
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $imageFieldOrder,
|
||||
'is_active' => 1,
|
||||
])
|
||||
->all();
|
||||
|
||||
if (empty($definitions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definitionPriority = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$priority = array_search((string)$definition->field_key, $imageFieldOrder, true);
|
||||
$definitionPriority[(int)$definition->id] = $priority === false ? 999 : (int)$priority;
|
||||
}
|
||||
|
||||
if (empty($definitionPriority)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$valueRows = AnimalFieldValue::find()
|
||||
->where(['animal_id' => $animalIds, 'field_definition_id' => array_keys($definitionPriority)])
|
||||
->all();
|
||||
|
||||
$imageUrls = [];
|
||||
$chosenPriorityByAnimal = [];
|
||||
foreach ($valueRows as $valueRow) {
|
||||
$animalId = (int)$valueRow->animal_id;
|
||||
$valueText = trim((string)$valueRow->value_text);
|
||||
if ($valueText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$priority = $definitionPriority[(int)$valueRow->field_definition_id] ?? 999;
|
||||
if (!isset($chosenPriorityByAnimal[$animalId]) || $priority < $chosenPriorityByAnimal[$animalId]) {
|
||||
$chosenPriorityByAnimal[$animalId] = $priority;
|
||||
$imageUrls[$animalId] = $valueText;
|
||||
}
|
||||
}
|
||||
|
||||
$missingAnimalIds = [];
|
||||
foreach ($animalIds as $animalId) {
|
||||
if (!isset($imageUrls[$animalId])) {
|
||||
$missingAnimalIds[] = (int)$animalId;
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowGalleryFallback && !empty($missingAnimalIds) && Yii::$app->db->schema->getTableSchema('rescue_animal_gallery_item', true) !== null) {
|
||||
$galleryItems = AnimalGalleryItem::find()
|
||||
->where(['animal_id' => $missingAnimalIds])
|
||||
->orderBy(['animal_id' => SORT_ASC, 'id' => SORT_DESC])
|
||||
->all();
|
||||
|
||||
foreach ($galleryItems as $galleryItem) {
|
||||
$animalId = (int)$galleryItem->animal_id;
|
||||
if (isset($imageUrls[$animalId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = trim((string)$galleryItem->getImageUrl());
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imageUrls[$animalId] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $imageUrls;
|
||||
}
|
||||
|
||||
private function resolveDisplayFieldOverrides(array $animalIds, string $fieldKey): array
|
||||
{
|
||||
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
|
||||
if (empty($animalIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!class_exists(RescueFieldDefinition::class)
|
||||
|| Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null
|
||||
|| Yii::$app->db->schema->getTableSchema('rescue_animal_field_value', true) === null
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definition = RescueFieldDefinition::findOne([
|
||||
'module_id' => 'animal_management',
|
||||
'group_key' => 'animal_profile',
|
||||
'field_key' => $fieldKey,
|
||||
]);
|
||||
|
||||
if (!$definition instanceof RescueFieldDefinition) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = AnimalFieldValue::find()
|
||||
->where(['animal_id' => $animalIds, 'field_definition_id' => (int)$definition->id])
|
||||
->all();
|
||||
|
||||
$allowed = array_keys(DisplaySettingsForm::fieldOptions());
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$raw = trim((string)$row->value_text);
|
||||
if ($raw === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
$decoded = array_map('trim', explode(',', $raw));
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($decoded as $field) {
|
||||
$field = trim((string)$field);
|
||||
if ($field === '' || !in_array($field, $allowed, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($field, $normalized, true)) {
|
||||
$normalized[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($normalized)) {
|
||||
$result[(int)$row->animal_id] = $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
26
widgets/stream/AnimalStreamEntryWallEntry.php
Normal file
26
widgets/stream/AnimalStreamEntryWallEntry.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\animal_management\widgets\stream;
|
||||
|
||||
use humhub\modules\animal_management\models\AnimalStreamEntry;
|
||||
use humhub\modules\content\widgets\stream\WallStreamModuleEntryWidget;
|
||||
|
||||
class AnimalStreamEntryWallEntry extends WallStreamModuleEntryWidget
|
||||
{
|
||||
/**
|
||||
* @var AnimalStreamEntry
|
||||
*/
|
||||
public $model;
|
||||
|
||||
public function renderContent()
|
||||
{
|
||||
return $this->render('wall-entry', [
|
||||
'entry' => $this->model,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTitle()
|
||||
{
|
||||
return $this->model->getContentDescription();
|
||||
}
|
||||
}
|
||||
228
widgets/stream/views/wall-entry.php
Normal file
228
widgets/stream/views/wall-entry.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\helpers\DateDisplayHelper;
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\animal_management\models\AnimalProgressUpdate;
|
||||
use humhub\modules\animal_management\models\AnimalStreamEntry;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var AnimalStreamEntry $entry */
|
||||
|
||||
$animal = $entry->animal;
|
||||
if (!$animal instanceof Animal) {
|
||||
return;
|
||||
}
|
||||
|
||||
$medicalVisit = $entry->medicalVisit;
|
||||
$progressUpdate = $entry->progressUpdate;
|
||||
$isMedical = (string)$entry->entry_type === AnimalStreamEntry::TYPE_MEDICAL && $medicalVisit instanceof AnimalMedicalVisit;
|
||||
$isProgress = (string)$entry->entry_type === AnimalStreamEntry::TYPE_PROGRESS && $progressUpdate instanceof AnimalProgressUpdate;
|
||||
|
||||
if (!$isMedical && !$isProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
$animalName = $animal->getDisplayName();
|
||||
|
||||
$mediaReference = '';
|
||||
$chipRows = [];
|
||||
$dateText = '';
|
||||
$detailRows = [];
|
||||
|
||||
if ($isProgress) {
|
||||
$customValues = $progressUpdate->getCustomFieldDisplayValues(true);
|
||||
$additionalFields = [];
|
||||
foreach ($customValues as $customField) {
|
||||
if ((string)($customField['field_key'] ?? '') === 'media_reference') {
|
||||
$mediaReference = trim((string)$customField['value']);
|
||||
continue;
|
||||
}
|
||||
|
||||
$additionalFields[] = [
|
||||
'label' => (string)$customField['label'],
|
||||
'value' => (string)$customField['value'],
|
||||
];
|
||||
}
|
||||
|
||||
$dateText = DateDisplayHelper::format((string)$progressUpdate->update_at);
|
||||
if (trim((string)$progressUpdate->weight) !== '') {
|
||||
$chipRows[] = Yii::t('AnimalManagementModule.base', 'Weight') . ': ' . trim((string)$progressUpdate->weight);
|
||||
}
|
||||
if (trim((string)$progressUpdate->vitals) !== '') {
|
||||
$chipRows[] = Yii::t('AnimalManagementModule.base', 'Vitals');
|
||||
}
|
||||
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Animal'), 'value' => $animalName];
|
||||
if (!empty($progressUpdate->vitals)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Vitals'), 'value' => (string)$progressUpdate->vitals];
|
||||
}
|
||||
if (!empty($progressUpdate->behavior_notes)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Behavior'), 'value' => (string)$progressUpdate->behavior_notes];
|
||||
}
|
||||
if (!empty($progressUpdate->meal_plan_changes)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Meal Plan'), 'value' => (string)$progressUpdate->meal_plan_changes];
|
||||
}
|
||||
if (!empty($progressUpdate->housing_changes)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Housing'), 'value' => (string)$progressUpdate->housing_changes];
|
||||
}
|
||||
if (!empty($progressUpdate->medical_concerns)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Medical Concerns'), 'value' => (string)$progressUpdate->medical_concerns];
|
||||
}
|
||||
if (!empty($additionalFields)) {
|
||||
$text = '';
|
||||
foreach ($additionalFields as $field) {
|
||||
$text .= $field['label'] . ': ' . $field['value'] . "\n";
|
||||
}
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Additional Fields'), 'value' => trim($text)];
|
||||
}
|
||||
} elseif ($isMedical) {
|
||||
$hiddenMedicalKeys = [
|
||||
'second_physician_name',
|
||||
'second_physician_business_name',
|
||||
'second_physician_street_address',
|
||||
'second_physician_city',
|
||||
'second_physician_state',
|
||||
'second_physician_zip',
|
||||
'second_physician_cell_phone',
|
||||
'second_physician_business_phone',
|
||||
'second_physician_license_number',
|
||||
'previous_physicians',
|
||||
];
|
||||
|
||||
$knownMedicalKeys = [
|
||||
'weight',
|
||||
'pulse',
|
||||
'blood_pressure',
|
||||
'oxygen',
|
||||
'chronic_conditions',
|
||||
'acute_conditions',
|
||||
'special_needs',
|
||||
'date_of_most_recent_medical_visit',
|
||||
'physician_name',
|
||||
'physician_business_name',
|
||||
'physician_street_address',
|
||||
'physician_city',
|
||||
'physician_state',
|
||||
'physician_zip',
|
||||
'physician_cell_phone',
|
||||
'physician_business_phone',
|
||||
'physician_license_number',
|
||||
'medical_media_reference',
|
||||
'media_reference',
|
||||
];
|
||||
|
||||
$vitalLabelOverrides = [
|
||||
'blood_pressure' => 'BP',
|
||||
'oxygen' => 'O₂',
|
||||
];
|
||||
|
||||
$fieldsByKey = [];
|
||||
$additionalFields = [];
|
||||
foreach ($medicalVisit->getCustomFieldDisplayValues(true) as $customField) {
|
||||
$fieldKey = (string)($customField['field_key'] ?? '');
|
||||
if (in_array($fieldKey, $hiddenMedicalKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldValue = trim((string)($customField['value'] ?? ''));
|
||||
if ($fieldValue === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fieldKey === 'medical_media_reference' || $fieldKey === 'media_reference') {
|
||||
$mediaReference = $fieldValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($fieldKey, $knownMedicalKeys, true)) {
|
||||
$fieldsByKey[$fieldKey] = [
|
||||
'label' => (string)($vitalLabelOverrides[$fieldKey] ?? ($customField['label'] ?? $fieldKey)),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$additionalFields[] = [
|
||||
'label' => (string)($customField['label'] ?? $fieldKey),
|
||||
'value' => $fieldValue,
|
||||
];
|
||||
}
|
||||
|
||||
$dateText = DateDisplayHelper::format((string)$medicalVisit->visit_at);
|
||||
foreach (['weight', 'pulse', 'blood_pressure', 'oxygen'] as $vitalKey) {
|
||||
if (!empty($fieldsByKey[$vitalKey]['value'])) {
|
||||
$chipRows[] = $fieldsByKey[$vitalKey]['label'] . ': ' . $fieldsByKey[$vitalKey]['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Animal'), 'value' => $animalName];
|
||||
if (!empty($medicalVisit->provider_name)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Provider'), 'value' => (string)$medicalVisit->provider_name];
|
||||
}
|
||||
if (!empty($medicalVisit->notes)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Clinical Notes'), 'value' => (string)$medicalVisit->notes];
|
||||
}
|
||||
if (!empty($medicalVisit->recommendations)) {
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Recommendations'), 'value' => (string)$medicalVisit->recommendations];
|
||||
}
|
||||
|
||||
foreach (['chronic_conditions', 'acute_conditions', 'special_needs'] as $conditionKey) {
|
||||
if (empty($fieldsByKey[$conditionKey]['value'])) {
|
||||
continue;
|
||||
}
|
||||
$detailRows[] = [
|
||||
'label' => (string)$fieldsByKey[$conditionKey]['label'],
|
||||
'value' => (string)$fieldsByKey[$conditionKey]['value'],
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($fieldsByKey['date_of_most_recent_medical_visit']['value'])) {
|
||||
$detailRows[] = [
|
||||
'label' => (string)$fieldsByKey['date_of_most_recent_medical_visit']['label'],
|
||||
'value' => DateDisplayHelper::format((string)$fieldsByKey['date_of_most_recent_medical_visit']['value']),
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($additionalFields)) {
|
||||
$text = '';
|
||||
foreach ($additionalFields as $field) {
|
||||
$text .= $field['label'] . ': ' . $field['value'] . "\n";
|
||||
}
|
||||
$detailRows[] = ['label' => Yii::t('AnimalManagementModule.base', 'Additional Fields'), 'value' => trim($text)];
|
||||
}
|
||||
}
|
||||
|
||||
$hasMediaImage = $mediaReference !== '' && (preg_match('/^https?:\/\//i', $mediaReference) || substr($mediaReference, 0, 1) === '/');
|
||||
?>
|
||||
|
||||
<div style="position:relative;overflow:hidden;border:1px solid #d5dfe8;border-radius:12px;background:#223446;margin:6px 0;min-height:240px;box-shadow:0 8px 22px rgba(12,24,36,0.16);">
|
||||
<?php if ($hasMediaImage): ?>
|
||||
<img src="<?= Html::encode($mediaReference) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Animal stream media') ?>" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;">
|
||||
<?php endif; ?>
|
||||
<div style="position:absolute;inset:0;background:linear-gradient(110deg,rgba(10,18,28,0.28) 10%,rgba(10,18,28,0.55) 52%,rgba(10,18,28,0.75) 100%);"></div>
|
||||
<div style="position:relative;z-index:1;min-height:240px;padding:14px;display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
|
||||
<div style="width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
||||
<span style="font-size:15px;font-weight:700;color:#ffffff;margin-right:auto;"><?= Html::encode($dateText) ?></span>
|
||||
<?php if (!empty($chipRows)): ?>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:0;justify-content:flex-end;">
|
||||
<?php foreach ($chipRows as $chip): ?>
|
||||
<span style="display:inline-block;border-radius:999px;border:1px solid rgba(255,255,255,0.3);background:rgba(255,255,255,0.16);color:#ffffff;font-size:12px;padding:4px 10px;"><?= Html::encode($chip) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="width:55%;max-width:55%;min-width:0;margin-left:auto;border-radius:10px;border:1px solid rgba(255,255,255,0.22);background:rgba(10,18,28,0.5);backdrop-filter:blur(2px);padding:12px;color:#ecf2f8;">
|
||||
<div style="color:#ffffff;font-size:13px;font-weight:700;margin-bottom:8px;">
|
||||
<?= Html::encode($isMedical
|
||||
? Yii::t('AnimalManagementModule.base', 'Medical Visit')
|
||||
: Yii::t('AnimalManagementModule.base', 'Progress Update')) ?>
|
||||
</div>
|
||||
<?php foreach ($detailRows as $row): ?>
|
||||
<div style="font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:rgba(231,241,249,0.78);margin-bottom:4px;"><?= Html::encode((string)$row['label']) ?></div>
|
||||
<div style="color:#eff5fb;margin-bottom:10px;"><?= nl2br(Html::encode((string)$row['value'])) ?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
95
widgets/views/searchAnimalProfilesBlock.php
Normal file
95
widgets/views/searchAnimalProfilesBlock.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use humhub\modules\animal_management\models\Animal;
|
||||
use humhub\modules\animal_management\models\AnimalMedicalVisit;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var Animal[] $animals */
|
||||
/* @var ContentContainerActiveRecord $contentContainer */
|
||||
/* @var string $queryValue */
|
||||
/* @var string $heading */
|
||||
/* @var array $tileFields */
|
||||
/* @var array<int, array> $tileFieldOverrides */
|
||||
/* @var array<int, AnimalMedicalVisit> $latestMedicalVisitByAnimal */
|
||||
/* @var array<int, string> $animalImageUrls */
|
||||
/* @var int $totalCount */
|
||||
/* @var int $displayCount */
|
||||
/* @var bool $hasMore */
|
||||
/* @var int $nextCount */
|
||||
/* @var bool $showAll */
|
||||
|
||||
$moduleEnabled = $contentContainer->moduleManager->isEnabled('animal_management');
|
||||
$currentParams = Yii::$app->request->getQueryParams();
|
||||
$buildProfileUrl = static function (array $overrides) use ($contentContainer, $currentParams): string {
|
||||
$params = array_merge($currentParams, $overrides);
|
||||
return $contentContainer->createUrl('/space_profiles/profile/view', $params);
|
||||
};
|
||||
?>
|
||||
|
||||
<h4 style="margin-top:0;"><?= Html::encode($heading) ?></h4>
|
||||
|
||||
<?php if ($moduleEnabled): ?>
|
||||
<form method="get" action="<?= Html::encode($contentContainer->createUrl('/space_profiles/profile/view')) ?>" style="margin-bottom:12px;">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="q"
|
||||
value="<?= Html::encode($queryValue) ?>"
|
||||
placeholder="<?= Html::encode(Yii::t('AnimalManagementModule.base', 'Search by name, species, or ID')) ?>"
|
||||
>
|
||||
<input type="hidden" name="animalFeedCount" value="10">
|
||||
<input type="hidden" name="animalFeedAll" value="0">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="submit"><?= Yii::t('AnimalManagementModule.base', 'Search') ?></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="well well-sm" style="margin-bottom:12px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Animal profiles are not enabled for this rescue.') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($animals)): ?>
|
||||
<div class="text-muted" style="margin-bottom:10px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'No matching animal profiles found.') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row" style="margin-bottom:6px;">
|
||||
<?php foreach ($animals as $animal): ?>
|
||||
<?php $animalId = (int)$animal->id; ?>
|
||||
<div class="col-xs-12" style="margin-bottom:12px;">
|
||||
<?= $this->renderFile(Yii::getAlias('@app/modules/animal_management/views/animals/_tile.php'), [
|
||||
'animal' => $animal,
|
||||
'contentContainer' => $contentContainer,
|
||||
'lastMedical' => $latestMedicalVisitByAnimal[$animalId] ?? null,
|
||||
'imageUrl' => $animalImageUrls[$animalId] ?? '',
|
||||
'tileFields' => $tileFieldOverrides[$animalId] ?? $tileFields,
|
||||
'showMedicalIcon' => true,
|
||||
]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($moduleEnabled): ?>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<?php if ($hasMore): ?>
|
||||
<a class="btn btn-default btn-sm" href="<?= Html::encode($buildProfileUrl(['animalFeedCount' => $nextCount, 'animalFeedAll' => 0])) ?>">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Show More') ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if (!$showAll && $totalCount > 0): ?>
|
||||
<a class="btn btn-default btn-sm" href="<?= Html::encode($buildProfileUrl(['animalFeedAll' => 1])) ?>">
|
||||
<?= Yii::t('AnimalManagementModule.base', 'Show All') ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($totalCount > 0): ?>
|
||||
<span class="text-muted" style="font-size:12px;">
|
||||
<?= Yii::t('AnimalManagementModule.base', '{shown} of {total} shown', ['shown' => $displayCount, 'total' => $totalCount]) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user