chore: sync module from working instance and add install guide

This commit is contained in:
Kelin Rescue Hub
2026-04-09 14:11:34 -04:00
parent 20adb1bd1e
commit 039c12233e
23 changed files with 4577 additions and 394 deletions

55
INSTALL.md Normal file
View File

@@ -0,0 +1,55 @@
# Animal Management Installation Guide
This guide installs the `animal_management` module in a reusable way for any HumHub instance.
## 1. Requirements
- HumHub `1.14+`
- Module directory access on the target instance
- Optional but recommended: `rescue_foundation` module
## 2. Clone into HumHub Modules Directory
The folder name must be exactly `animal_management`.
```bash
git clone https://gitea.kelinreij.duckdns.org/humhub-modules/animal-management.git \
/var/www/localhost/htdocs/protected/modules/animal_management
```
If the folder already exists:
```bash
cd /var/www/localhost/htdocs/protected/modules/animal_management
git pull
```
## 3. Enable the Module
In HumHub UI:
1. Go to `Administration` -> `Modules`.
2. Enable `Animal Management`.
3. Enable it per space where needed.
## 4. Run Migrations
From the HumHub app host/container:
```bash
php /var/www/localhost/htdocs/protected/yii migrate/up \
--include-module-migrations=1 --interactive=0
```
## 5. Verify
1. Open a space where the module is enabled.
2. Confirm Animals views load.
3. Confirm medical/progress/transfer flows render without errors.
## Docker Example
```bash
docker exec humhub php /var/www/localhost/htdocs/protected/yii migrate/up \
--include-module-migrations=1 --interactive=0
```

View File

@@ -42,6 +42,8 @@ class AnimalsController extends ContentContainerController
'add-medical-visit' => ['post'],
'add-progress-update' => ['post'],
'add-gallery-images' => ['post'],
'add-gallery-images-inline' => ['get', 'post'],
'transfer-inline' => ['get', 'post'],
'remove-gallery-image' => ['post'],
'transfer-respond' => ['post'],
'transfer-complete' => ['post'],
@@ -55,7 +57,7 @@ class AnimalsController extends ContentContainerController
protected function getAccessRules()
{
return [
[ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [Space::USERGROUP_OWNER, Space::USERGROUP_ADMIN, Space::USERGROUP_MODERATOR], 'actions' => ['create', 'edit', 'delete', 'transfer', 'add-medical-visit', 'add-medical-visit-inline', 'edit-medical-visit', 'add-progress-update', 'add-progress-update-inline', 'edit-progress-update', 'add-gallery-images', 'remove-gallery-image', 'transfer-respond', 'transfer-complete', 'transfer-cancel']],
[ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [Space::USERGROUP_OWNER, Space::USERGROUP_ADMIN, Space::USERGROUP_MODERATOR], 'actions' => ['create', 'edit', 'delete', 'transfer', 'transfer-inline', 'add-medical-visit', 'add-medical-visit-inline', 'edit-medical-visit', 'add-progress-update', 'add-progress-update-inline', 'edit-progress-update', 'add-gallery-images', 'add-gallery-images-inline', 'remove-gallery-image', 'transfer-respond', 'transfer-complete', 'transfer-cancel']],
];
}
@@ -68,9 +70,13 @@ class AnimalsController extends ContentContainerController
GalleryIntegrationService::syncSpaceAnimalGalleries($this->contentContainer);
}
$viewMode = trim((string)Yii::$app->request->get('view', 'tiles'));
if (!in_array($viewMode, ['tiles', 'table'], true)) {
if (!in_array($viewMode, ['tiles', 'tiles2', 'rows', 'tablet', 'table'], true)) {
$viewMode = 'tiles';
}
$isFocusModeRequest = trim((string)Yii::$app->request->get('focus', '')) === '1';
if ($isFocusModeRequest) {
$viewMode = 'tablet';
}
$sortKey = trim((string)Yii::$app->request->get('sort', 'updated_at'));
$sortDirection = strtolower(trim((string)Yii::$app->request->get('direction', 'desc'))) === 'asc' ? 'asc' : 'desc';
@@ -165,6 +171,33 @@ class AnimalsController extends ContentContainerController
return (int)$animal->id;
}, $animals);
$animalDonationGoalsByAnimal = [];
$donationGoalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
if (!empty($animalIds)
&& $this->contentContainer instanceof Space
&& $this->contentContainer->moduleManager->isEnabled('donations')
&& class_exists($donationGoalClass)
&& Yii::$app->db->schema->getTableSchema($donationGoalClass::tableName(), true) !== null
) {
$donationGoals = $donationGoalClass::find()
->where([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
'goal_type' => $donationGoalClass::TYPE_ANIMAL,
])
->andWhere(['target_animal_id' => $animalIds])
->orderBy(['is_active' => SORT_DESC, 'id' => SORT_DESC])
->all();
foreach ($donationGoals as $goal) {
$animalId = (int)$goal->target_animal_id;
if ($animalId <= 0 || isset($animalDonationGoalsByAnimal[$animalId])) {
continue;
}
$animalDonationGoalsByAnimal[$animalId] = $goal;
}
}
$animalImageUrls = $this->resolveAnimalImageUrls($animalIds, ['profile_image_url', 'profile_image', 'photo_url', 'image_url', 'photo'], false);
$tileFieldOverrides = $this->resolveDisplayFieldOverrides($animalIds, 'tile_display_fields');
@@ -245,6 +278,7 @@ class AnimalsController extends ContentContainerController
'animalImageUrls' => $animalImageUrls,
'tileFields' => $this->getTileFieldSettings(),
'tileFieldOverrides' => $tileFieldOverrides,
'animalDonationGoalsByAnimal' => $animalDonationGoalsByAnimal,
'space' => $this->contentContainer,
'canManage' => $this->canManageAnimals(),
'incomingTransfers' => $incomingTransfers,
@@ -257,6 +291,10 @@ class AnimalsController extends ContentContainerController
{
$animal = $this->findAnimal($id);
$canManage = $this->canManageAnimals();
$layoutMode = trim((string)Yii::$app->request->get('layout', 'default'));
if (!in_array($layoutMode, ['default', 'tablet'], true)) {
$layoutMode = 'default';
}
if ($this->contentContainer instanceof Space) {
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
@@ -279,8 +317,10 @@ class AnimalsController extends ContentContainerController
$transferEvents = $animal->getTransferEvents()->orderBy(['id' => SORT_DESC])->limit(100)->all();
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
$customFieldValues = $animal->getCustomFieldDisplayValues($canManage);
$animalImageUrls = $this->resolveAnimalImageUrls([(int)$animal->id], ['cover_image_url', 'image_url', 'photo_url', 'photo'], false);
$animalCoverImageUrl = trim((string)($animalImageUrls[(int)$animal->id] ?? ''));
$animalCoverImageUrls = $this->resolveAnimalImageUrls([(int)$animal->id], ['cover_image_url', 'image_url', 'photo_url', 'photo'], false);
$animalProfileImageUrls = $this->resolveAnimalImageUrls([(int)$animal->id], ['profile_image_url', 'profile_image', 'image_url', 'photo_url', 'photo'], false);
$animalCoverImageUrl = trim((string)($animalCoverImageUrls[(int)$animal->id] ?? ''));
$animalProfileImageUrl = trim((string)($animalProfileImageUrls[(int)$animal->id] ?? ''));
$detailHeroFields = $this->getDetailHeroFieldSettings();
$heroOverrides = $this->resolveDisplayFieldOverrides([(int)$animal->id], 'hero_display_fields');
if (!empty($heroOverrides[(int)$animal->id])) {
@@ -298,7 +338,9 @@ class AnimalsController extends ContentContainerController
'galleryItems' => $galleryItems,
'customFieldValues' => $customFieldValues,
'animalCoverImageUrl' => $animalCoverImageUrl,
'animalProfileImageUrl' => $animalProfileImageUrl,
'detailHeroFields' => $detailHeroFields,
'layoutMode' => $layoutMode,
]);
}
@@ -352,6 +394,7 @@ class AnimalsController extends ContentContainerController
public function actionCreate()
{
$model = new AnimalForm(['contentContainer' => $this->contentContainer]);
$intakeGoalPayload = (array)Yii::$app->request->post('IntakeGoal', []);
if (Yii::$app->request->isPost) {
$model->load(Yii::$app->request->post());
@@ -368,6 +411,11 @@ class AnimalsController extends ContentContainerController
if ($this->contentContainer instanceof Space) {
GalleryIntegrationService::ensureAnimalGallery($savedAnimal, $this->contentContainer);
}
$goalError = $this->createIntakeAnimalGoal($savedAnimal, $intakeGoalPayload);
if ($goalError !== null) {
$this->view->error($goalError);
}
}
$this->view->success(Yii::t('AnimalManagementModule.base', 'Animal created.'));
@@ -379,6 +427,7 @@ class AnimalsController extends ContentContainerController
'space' => $this->contentContainer,
'isEdit' => false,
'animal' => null,
'showIntakeGoalSection' => $this->canUseIntakeGoalSection(),
]);
}
@@ -460,6 +509,7 @@ class AnimalsController extends ContentContainerController
public function actionTransfer(int $id)
{
$animal = $this->findAnimal($id);
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
$form = new TransferRequestForm([
'animal' => $animal,
@@ -468,107 +518,74 @@ class AnimalsController extends ContentContainerController
if ($form->load(Yii::$app->request->post()) && $form->save()) {
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request sent.'));
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]));
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
}
return $this->render('transfer', [
'space' => $this->contentContainer,
'animal' => $animal,
'model' => $form,
'isInline' => false,
'returnTo' => $returnTo,
]);
}
public function actionTransferInline(int $id)
{
$animal = $this->findAnimal($id);
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
$form = new TransferRequestForm([
'animal' => $animal,
'sourceSpace' => $this->contentContainer,
]);
if ($form->load(Yii::$app->request->post()) && $form->save()) {
$this->view->success(Yii::t('AnimalManagementModule.base', 'Transfer request sent.'));
if ($isInline) {
return $this->renderAjax('inline-edit-result', [
'collapseId' => 'transfer-add-inline',
'refreshSelectors' => ['#animal-transfer-panel'],
]);
}
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
}
$params = [
'space' => $this->contentContainer,
'animal' => $animal,
'model' => $form,
'isInline' => $isInline,
'returnTo' => $returnTo,
];
if ($isInline) {
return $this->renderAjax('transfer', $params);
}
return $this->render('transfer', $params);
}
public function actionAddGalleryImages(int $id)
{
$animal = $this->findAnimal($id);
$uploadedFiles = UploadedFile::getInstancesByName('galleryImages');
$maxUploadCount = 10;
$result = $this->processGalleryUploads($animal);
if (empty($uploadedFiles)) {
if (!$result['hadSelection']) {
$this->view->error(Yii::t('AnimalManagementModule.base', 'No gallery images were selected.'));
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]) . '#animal-gallery');
}
if (count($uploadedFiles) > $maxUploadCount) {
$uploadedFiles = array_slice($uploadedFiles, 0, $maxUploadCount);
$this->view->info(Yii::t('AnimalManagementModule.base', 'Only the first {count} selected images were processed.', ['count' => $maxUploadCount]));
if ($result['wasLimited']) {
$this->view->info(Yii::t('AnimalManagementModule.base', 'Only the first {count} selected images were processed.', ['count' => $result['maxUploadCount']]));
}
$allowedExtensions = array_map('strtolower', UploadStandards::imageExtensions());
$allowedMimeTypes = array_map('strtolower', UploadStandards::imageMimeTypes());
$maxBytes = (int)UploadStandards::IMAGE_MAX_BYTES;
$existingItems = AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->all();
$existingHashes = [];
foreach ($existingItems as $existingItem) {
$existingUrl = trim((string)$existingItem->getImageUrl());
if ($existingUrl === '') {
continue;
}
$hash = $this->computeImageContentHash($existingUrl);
if ($hash !== null) {
$existingHashes[$hash] = true;
}
}
$added = 0;
foreach ($uploadedFiles as $uploadedFile) {
if (!$uploadedFile instanceof UploadedFile) {
continue;
}
$extension = strtolower((string)$uploadedFile->extension);
$mimeType = strtolower((string)$uploadedFile->type);
if (!in_array($extension, $allowedExtensions, true)) {
continue;
}
if ($mimeType !== '' && !in_array($mimeType, $allowedMimeTypes, true)) {
continue;
}
if ($uploadedFile->size > $maxBytes) {
continue;
}
$storedPath = $this->storeGalleryUpload($animal, $uploadedFile);
if ($storedPath === null) {
continue;
}
$storedHash = $this->computeImageContentHash($storedPath);
$exactExists = AnimalGalleryItem::find()
->where(['animal_id' => (int)$animal->id, 'file_path' => $storedPath])
->exists();
if ($exactExists || ($storedHash !== null && isset($existingHashes[$storedHash]))) {
$absolute = Yii::getAlias('@webroot') . $storedPath;
if (is_file($absolute)) {
@unlink($absolute);
}
continue;
}
$item = new AnimalGalleryItem();
$item->animal_id = (int)$animal->id;
$item->file_path = $storedPath;
$item->source_type = 'upload';
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
if ($item->save()) {
$added++;
if ($storedHash !== null) {
$existingHashes[$storedHash] = true;
}
}
}
if ($added > 0) {
if ($result['added'] > 0) {
if ($this->contentContainer instanceof Space) {
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
}
$this->view->success(Yii::t('AnimalManagementModule.base', '{count} image(s) added to gallery.', ['count' => $added]));
$this->view->success(Yii::t('AnimalManagementModule.base', '{count} image(s) added to gallery.', ['count' => $result['added']]));
} else {
$this->view->error(Yii::t('AnimalManagementModule.base', 'No gallery images were added. Check image type/size requirements.'));
}
@@ -576,6 +593,63 @@ class AnimalsController extends ContentContainerController
return $this->redirect($this->contentContainer->createUrl('/animal_management/animals/view', ['id' => $animal->id]) . '#animal-gallery');
}
public function actionAddGalleryImagesInline(int $id)
{
$animal = $this->findAnimal($id);
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'view'));
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
$maxUploadCount = 10;
$errorMessage = '';
$infoMessage = '';
if (Yii::$app->request->isPost) {
$result = $this->processGalleryUploads($animal);
if (!$result['hadSelection']) {
$errorMessage = Yii::t('AnimalManagementModule.base', 'No gallery images were selected.');
} else {
if ($result['wasLimited']) {
$infoMessage = Yii::t('AnimalManagementModule.base', 'Only the first {count} selected images were processed.', ['count' => $result['maxUploadCount']]);
}
if ($result['added'] > 0) {
if ($this->contentContainer instanceof Space) {
GalleryIntegrationService::ensureAnimalGallery($animal, $this->contentContainer);
}
$this->view->success(Yii::t('AnimalManagementModule.base', '{count} image(s) added to gallery.', ['count' => $result['added']]));
if ($isInline) {
return $this->renderAjax('inline-edit-result', [
'collapseId' => 'gallery-add-inline',
'refreshSelectors' => ['#animal-gallery-panel'],
]);
}
return $this->redirectToAnimalPage((int)$animal->id, $returnTo);
}
$errorMessage = Yii::t('AnimalManagementModule.base', 'No gallery images were added. Check image type/size requirements.');
}
}
$galleryItems = $animal->getGalleryItems()->orderBy(['id' => SORT_DESC])->limit(120)->all();
$params = [
'space' => $this->contentContainer,
'animal' => $animal,
'galleryItems' => $galleryItems,
'isInline' => $isInline,
'returnTo' => $returnTo,
'maxUploadCount' => $maxUploadCount,
'errorMessage' => $errorMessage,
'infoMessage' => $infoMessage,
];
if ($isInline) {
return $this->renderAjax('add-gallery-images-inline', $params);
}
return $this->render('add-gallery-images-inline', $params);
}
public function actionRemoveGalleryImage(int $id, int $galleryId)
{
$animal = $this->findAnimal($id);
@@ -646,6 +720,9 @@ class AnimalsController extends ContentContainerController
$animal = $this->findAnimal($id);
$this->ensureMedicalMediaFieldDefinition();
$form = new AnimalMedicalVisitForm(['animal' => $animal]);
if (trim((string)$form->visit_at) === '') {
$form->visit_at = date('Y-m-d\TH:i');
}
$returnTo = (string)Yii::$app->request->post('returnTo', Yii::$app->request->get('returnTo', 'medical-visits'));
$isInline = (int)Yii::$app->request->get('inline', 1) === 1;
@@ -1258,6 +1335,105 @@ class AnimalsController extends ContentContainerController
return $result;
}
private function processGalleryUploads(Animal $animal): array
{
$uploadedFiles = UploadedFile::getInstancesByName('galleryImages');
$maxUploadCount = 10;
$hadSelection = !empty($uploadedFiles);
$wasLimited = false;
if (!$hadSelection) {
return [
'hadSelection' => false,
'wasLimited' => false,
'maxUploadCount' => $maxUploadCount,
'added' => 0,
];
}
if (count($uploadedFiles) > $maxUploadCount) {
$uploadedFiles = array_slice($uploadedFiles, 0, $maxUploadCount);
$wasLimited = true;
}
$allowedExtensions = array_map('strtolower', UploadStandards::imageExtensions());
$allowedMimeTypes = array_map('strtolower', UploadStandards::imageMimeTypes());
$maxBytes = (int)UploadStandards::IMAGE_MAX_BYTES;
$existingItems = AnimalGalleryItem::find()->where(['animal_id' => (int)$animal->id])->all();
$existingHashes = [];
foreach ($existingItems as $existingItem) {
$existingUrl = trim((string)$existingItem->getImageUrl());
if ($existingUrl === '') {
continue;
}
$hash = $this->computeImageContentHash($existingUrl);
if ($hash !== null) {
$existingHashes[$hash] = true;
}
}
$added = 0;
foreach ($uploadedFiles as $uploadedFile) {
if (!$uploadedFile instanceof UploadedFile) {
continue;
}
$extension = strtolower((string)$uploadedFile->extension);
$mimeType = strtolower((string)$uploadedFile->type);
if (!in_array($extension, $allowedExtensions, true)) {
continue;
}
if ($mimeType !== '' && !in_array($mimeType, $allowedMimeTypes, true)) {
continue;
}
if ($uploadedFile->size > $maxBytes) {
continue;
}
$storedPath = $this->storeGalleryUpload($animal, $uploadedFile);
if ($storedPath === null) {
continue;
}
$storedHash = $this->computeImageContentHash($storedPath);
$exactExists = AnimalGalleryItem::find()
->where(['animal_id' => (int)$animal->id, 'file_path' => $storedPath])
->exists();
if ($exactExists || ($storedHash !== null && isset($existingHashes[$storedHash]))) {
$absolute = Yii::getAlias('@webroot') . $storedPath;
if (is_file($absolute)) {
@unlink($absolute);
}
continue;
}
$item = new AnimalGalleryItem();
$item->animal_id = (int)$animal->id;
$item->file_path = $storedPath;
$item->source_type = 'upload';
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
if ($item->save()) {
$added++;
if ($storedHash !== null) {
$existingHashes[$storedHash] = true;
}
}
}
return [
'hadSelection' => true,
'wasLimited' => $wasLimited,
'maxUploadCount' => $maxUploadCount,
'added' => $added,
];
}
private function storeGalleryUpload(Animal $animal, UploadedFile $file): ?string
{
$random = Yii::$app->security->generateRandomString(8);
@@ -1707,4 +1883,74 @@ class AnimalsController extends ContentContainerController
$item->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
$item->save();
}
private function canUseIntakeGoalSection(): bool
{
if (!($this->contentContainer instanceof Space)) {
return false;
}
if (!$this->contentContainer->moduleManager->isEnabled('donations')) {
return false;
}
$goalFormClass = 'humhub\\modules\\donations\\models\\forms\\DonationGoalForm';
$goalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
if (!class_exists($goalFormClass) || !class_exists($goalClass)) {
return false;
}
return Yii::$app->db->schema->getTableSchema($goalClass::tableName(), true) !== null;
}
private function createIntakeAnimalGoal(Animal $animal, array $payload): ?string
{
if ((int)($payload['enabled'] ?? 0) !== 1) {
return null;
}
if (!$this->canUseIntakeGoalSection()) {
return Yii::t('AnimalManagementModule.base', 'Animal was created, but donation goal setup is not available in this space.');
}
$goalFormClass = 'humhub\\modules\\donations\\models\\forms\\DonationGoalForm';
$goalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
$targetAmount = max(0.0, (float)($payload['target_amount'] ?? 0));
if ($targetAmount <= 0) {
return Yii::t('AnimalManagementModule.base', 'Animal was created, but the intake donation goal target must be greater than zero.');
}
$title = trim((string)($payload['title'] ?? ''));
if ($title === '') {
$title = Yii::t('AnimalManagementModule.base', '{animalName} Care Fund', ['animalName' => $animal->getDisplayName()]);
}
$description = trim((string)($payload['description'] ?? ''));
$imagePath = trim((string)($payload['image_path'] ?? ''));
$isActive = (int)($payload['is_active'] ?? 1) === 1;
$goalForm = new $goalFormClass();
$goalForm->contentContainer = $this->contentContainer;
$goalForm->goal_type = $goalClass::TYPE_ANIMAL;
$goalForm->target_animal_id = (int)$animal->id;
$goalForm->title = $title;
$goalForm->description = $description;
$goalForm->target_amount = $targetAmount;
$goalForm->is_active = $isActive;
if ($imagePath !== '') {
$goalForm->imageGalleryPath = $imagePath;
}
$savedGoal = $goalForm->save();
if ($savedGoal === null) {
$firstErrors = $goalForm->getFirstErrors();
$errorText = !empty($firstErrors) ? implode(' ', array_values($firstErrors)) : Yii::t('AnimalManagementModule.base', 'Unknown donation goal error.');
return Yii::t('AnimalManagementModule.base', 'Animal was created, but donation goal could not be saved: {error}', ['error' => $errorText]);
}
return null;
}
}

View File

@@ -5,11 +5,13 @@ 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\animal_management\services\ModuleSetupService;
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;
use yii\web\BadRequestHttpException;
class SettingsController extends ContentContainerController
{
@@ -53,6 +55,37 @@ class SettingsController extends ContentContainerController
'animalCount' => (int)$animalCount,
'fieldSettingsForm' => $fieldSettingsForm,
'displaySettingsForm' => $displaySettingsForm,
'space' => $this->contentContainer,
]);
}
public function actionSetup()
{
if (!Yii::$app->request->isPost) {
throw new BadRequestHttpException('Invalid request method.');
}
if (!$this->contentContainer instanceof Space) {
$this->view->error(Yii::t('AnimalManagementModule.base', 'Setup can only be run inside a space.'));
return $this->redirect($this->contentContainer->createUrl('/animal_management/settings'));
}
try {
$result = ModuleSetupService::runForSpace($this->contentContainer);
$appliedCount = count($result['applied'] ?? []);
if ($appliedCount > 0) {
$this->view->success(Yii::t('AnimalManagementModule.base', 'Setup completed. Applied {count} migration(s).', [
'count' => $appliedCount,
]));
} else {
$this->view->success(Yii::t('AnimalManagementModule.base', 'Setup completed. No pending migrations were found.'));
}
} catch (\Throwable $e) {
Yii::error($e, 'animal_management.setup');
$this->view->error(Yii::t('AnimalManagementModule.base', 'Setup failed. Please check logs and try again.'));
}
return $this->redirect($this->contentContainer->createUrl('/animal_management/settings'));
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace humhub\modules\animal_management\events;
use humhub\modules\animal_management\models\Animal;
use humhub\modules\content\components\ContentContainerActiveRecord;
use yii\base\Event;
class AnimalTileRenderEvent extends Event
{
public const EVENT_RENDER_OVERLAY = 'renderOverlay';
public Animal $animal;
public ContentContainerActiveRecord $contentContainer;
public $existingDonationGoal = null;
public bool $showDonationSettingsButton = false;
public string $donationToggleInputId = '';
public string $donationInlineFormId = '';
/** @var string[] */
private array $htmlFragments = [];
public function addHtml(string $html): void
{
$html = trim($html);
if ($html === '') {
return;
}
$this->htmlFragments[] = $html;
}
public function getHtml(): string
{
if (empty($this->htmlFragments)) {
return '';
}
return implode("\n", $this->htmlFragments);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace humhub\modules\animal_management\events;
use humhub\modules\animal_management\models\Animal;
use humhub\modules\content\components\ContentContainerActiveRecord;
use yii\base\Event;
class AnimalTileSizeEvent extends Event
{
public const EVENT_RESOLVE_SIZE = 'resolveSize';
public Animal $animal;
public ContentContainerActiveRecord $contentContainer;
public $existingDonationGoal = null;
private int $additionalHeightPx = 0;
public function addAdditionalHeightPx(int $heightPx): void
{
if ($heightPx <= 0) {
return;
}
$this->additionalHeightPx += $heightPx;
}
public function getAdditionalHeightPx(): int
{
return max(0, $this->additionalHeightPx);
}
}

View File

@@ -61,6 +61,8 @@ class AnimalStreamPublisherService
$entry->medical_visit_id = $medicalVisitId;
$entry->progress_update_id = $progressUpdateId;
$entry->content->container = $space;
// Respect each space's configured stream visibility policy.
$entry->content->visibility = (int)$space->getDefaultContentVisibility();
if (!$entry->save()) {
Yii::warning([

View File

@@ -0,0 +1,72 @@
<?php
namespace humhub\modules\animal_management\services;
use humhub\modules\space\models\Space;
use Yii;
class ModuleSetupService
{
public static function runForSpace(Space $space): array
{
$result = static::applyModuleMigrations();
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($space);
if ($settings->get('searchBlockHeading') === null) {
$settings->set('searchBlockHeading', 'Animals Available for Intake & Transfer');
$result['defaultsApplied'] = ['searchBlockHeading'];
} else {
$result['defaultsApplied'] = [];
}
return $result;
}
private static function applyModuleMigrations(): array
{
$migrationDir = dirname(__DIR__) . '/migrations';
$files = glob($migrationDir . '/m*.php') ?: [];
sort($files, SORT_NATURAL);
$existingVersions = Yii::$app->db->createCommand('SELECT version FROM migration')->queryColumn();
$history = array_fill_keys($existingVersions, true);
$applied = [];
$skipped = [];
foreach ($files as $file) {
$version = pathinfo($file, PATHINFO_FILENAME);
if (isset($history[$version])) {
$skipped[] = $version;
continue;
}
if (!class_exists($version, false)) {
require_once $file;
}
if (!class_exists($version, false)) {
throw new \RuntimeException('Migration class not found: ' . $version);
}
$migration = new $version();
$ok = method_exists($migration, 'safeUp') ? $migration->safeUp() : $migration->up();
if ($ok === false) {
throw new \RuntimeException('Migration failed: ' . $version);
}
Yii::$app->db->createCommand()->insert('migration', [
'version' => $version,
'apply_time' => time(),
])->execute();
$applied[] = $version;
$history[$version] = true;
}
return [
'applied' => $applied,
'skipped' => $skipped,
];
}
}

View File

@@ -0,0 +1,184 @@
<?php
use humhub\libs\Html;
/*
* @var array $donationHistoryRows
* @var bool $showDonationSettingsButton
* @var string $goalEditorToggleId
* @var int $existingGoalId
* @var string $createInlineGoalUrl
* @var string $existingGoalTitle
* @var float $existingGoalTargetAmount
* @var string $existingGoalDescription
* @var string $existingGoalImage
* @var array $galleryUrls
* @var \humhub\modules\animal_management\models\Animal $animal
* @var int $existingGoalActive
* @var string $fileFieldId
* @var string $previewId
* @var string $quickDonateToggleId
* @var string $quickDonatePanelId
*/
?>
<div class="animal-tile-quick-donate-layout<?= $showDonationSettingsButton ? ' has-goal-settings' : '' ?>">
<div class="animal-tile-quick-donate-history-col">
<div style="font-size:14px;font-weight:700;color:#f8fafc;margin-bottom:6px;"><?= Yii::t('DonationsModule.base', 'Donation History') ?></div>
<?php if (empty($donationHistoryRows)): ?>
<div style="font-size:12px;color:rgba(226,232,240,0.9);"><?= Yii::t('DonationsModule.base', 'No donations yet.') ?></div>
<?php else: ?>
<div class="animal-tile-scroll-region" style="flex:1 1 auto;min-height:0;overflow-y:auto;">
<?php foreach ($donationHistoryRows as $donationRow): ?>
<div class="animal-tile-donation-history-item">
<div class="animal-tile-donation-history-meta">
<?php if (!empty($donationRow['avatarUrl'])): ?>
<img class="animal-tile-donation-history-avatar" src="<?= Html::encode((string)$donationRow['avatarUrl']) ?>" alt="<?= Html::encode((string)$donationRow['donor']) ?>">
<?php else: ?>
<span class="animal-tile-donation-history-avatar animal-tile-donation-history-avatar-fallback">
<i class="fa fa-user"></i>
</span>
<?php endif; ?>
<div class="animal-tile-donation-history-identity">
<?php if (!empty($donationRow['donorUrl'])): ?>
<?= Html::a(
Html::encode($donationRow['donor']),
(string)$donationRow['donorUrl'],
['class' => 'animal-tile-donation-history-name']
) ?>
<?php else: ?>
<span class="animal-tile-donation-history-name"><?= Html::encode($donationRow['donor']) ?></span>
<?php endif; ?>
<span class="animal-tile-donation-history-when"><?= Html::encode((string)($donationRow['when'] ?? '')) ?></span>
</div>
</div>
<span class="animal-tile-donation-history-amount"><?= Html::encode($donationRow['amount']) ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
__ANIMAL_TILE_QUICK_DONATE_FORM_SLOT__
<?php if ($showDonationSettingsButton): ?>
<div class="animal-tile-quick-donate-goal-col">
<input type="checkbox" id="<?= Html::encode($goalEditorToggleId) ?>" class="animal-tile-goal-editor-toggle" <?= $existingGoalId > 0 ? 'checked' : '' ?>>
<?php if ($existingGoalId <= 0): ?>
<label for="<?= Html::encode($goalEditorToggleId) ?>" class="btn btn-default btn-xs animal-tile-set-goal-btn" style="margin:0 0 8px 0;align-self:flex-start;">
<i class="fa fa-plus"></i> <?= Yii::t('DonationsModule.base', 'Set Goal') ?>
</label>
<?php endif; ?>
<div class="animal-tile-goal-editor-fields animal-tile-scroll-region" style="<?= $existingGoalId > 0 ? 'display:flex;flex-direction:column;flex:1 1 auto;min-height:0;overflow-y:auto;' : 'display:none;flex-direction:column;flex:1 1 auto;min-height:0;overflow-y:auto;' ?>">
<?= Html::beginForm($createInlineGoalUrl, 'post', [
'enctype' => 'multipart/form-data',
'style' => 'margin:0;',
]) ?>
<?= Html::hiddenInput(Yii::$app->request->csrfParam, Yii::$app->request->getCsrfToken()) ?>
<?= Html::hiddenInput('DonationGoalForm[id]', $existingGoalId) ?>
<?= Html::hiddenInput('DonationGoalForm[goal_type]', 'animal') ?>
<?= Html::hiddenInput('DonationGoalForm[target_animal_id]', (int)$animal->id) ?>
<?= Html::hiddenInput('DonationGoalForm[is_active]', $existingGoalActive) ?>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;">
<div style="font-size:14px;font-weight:700;color:#f8fafc;"><?= Yii::t('DonationsModule.base', 'Goal Settings') ?></div>
<div style="display:flex;align-items:center;gap:8px;">
<button type="submit" class="animal-tile-goal-header-action" title="<?= Yii::t('DonationsModule.base', 'Save Goal') ?>" aria-label="<?= Yii::t('DonationsModule.base', 'Save Goal') ?>">
<i class="fa fa-check"></i>
</button>
<label for="<?= Html::encode($quickDonateToggleId) ?>" class="animal-tile-goal-header-action" title="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>" aria-label="<?= Yii::t('AnimalManagementModule.base', 'Close') ?>">
<i class="fa fa-times"></i>
</label>
</div>
</div>
<div class="animal-tile-goal-title-target-row" style="display:grid;grid-template-columns:minmax(0,1fr) minmax(0,180px);gap:10px;align-items:end;margin-bottom:8px;">
<div class="form-group" style="margin-bottom:0;min-width:0;">
<label style="font-size:12px;font-weight:600;margin-bottom:4px;display:block;"><?= Yii::t('DonationsModule.base', 'Title') ?></label>
<?= Html::textInput('DonationGoalForm[title]', $existingGoalTitle, [
'class' => 'form-control input-sm',
'maxlength' => 190,
'required' => true,
]) ?>
</div>
<div class="form-group" style="margin-bottom:0;min-width:0;">
<label style="font-size:12px;font-weight:600;margin-bottom:4px;display:block;"><?= Yii::t('DonationsModule.base', 'Target') ?></label>
<div class="input-group" style="width:100%;max-width:none;">
<span class="input-group-addon" style="background:rgba(255,255,255,0.12);border-color:rgba(255,255,255,0.24);color:#f8fafc;">$</span>
<?= Html::input('number', 'DonationGoalForm[target_amount]', $existingGoalTargetAmount > 0 ? (string)round($existingGoalTargetAmount) : '', [
'class' => 'form-control input-sm',
'step' => '1',
'min' => '1',
'inputmode' => 'numeric',
'required' => true,
]) ?>
</div>
</div>
</div>
<div class="form-group" style="margin-bottom:8px;">
<label style="font-size:12px;font-weight:600;margin-bottom:4px;display:block;"><?= Yii::t('DonationsModule.base', 'Description') ?></label>
<?= Html::textarea('DonationGoalForm[description]', $existingGoalDescription, [
'class' => 'form-control input-sm',
'rows' => 2,
]) ?>
</div>
<div class="form-group" style="margin-bottom:8px;">
<label style="font-size:12px;font-weight:600;margin-bottom:4px;display:block;"><?= Yii::t('DonationsModule.base', 'Image') ?></label>
<div class="animal-tile-goal-image-row" style="display:flex;align-items:flex-start;gap:10px;margin-bottom:8px;">
<div id="<?= Html::encode($previewId) ?>" class="animal-tile-goal-selected-image text-muted" style="font-size:12px;">
<?php if ($existingGoalImage !== ''): ?>
<img class="animal-tile-goal-selected-image-thumb" src="<?= Html::encode($existingGoalImage) ?>" alt="">
<?php else: ?>
<span class="animal-tile-goal-selected-image-empty"><?= Yii::t('DonationsModule.base', 'No image selected.') ?></span>
<?php endif; ?>
</div>
<div class="animal-tile-goal-image-list" style="display:flex;flex-wrap:wrap;gap:6px;min-width:0;flex:1 1 auto;align-content:flex-start;">
<?php if (!empty($galleryUrls)): ?>
<?php foreach ($galleryUrls as $galleryIndex => $galleryUrl): ?>
<?php $galleryOptionId = 'animal-donation-gallery-option-' . (int)$animal->id . '-' . (int)$galleryIndex; ?>
<div style="position:relative;">
<input
type="radio"
class="animal-donation-gallery-radio"
id="<?= Html::encode($galleryOptionId) ?>"
name="DonationGoalForm[imageGalleryPath]"
value="<?= Html::encode($galleryUrl) ?>"
data-preview="#<?= Html::encode($previewId) ?>"
data-editor-bg="#<?= Html::encode($quickDonatePanelId) ?>"
<?= $existingGoalImage === $galleryUrl ? 'checked' : '' ?>
>
<label
for="<?= Html::encode($galleryOptionId) ?>"
class="animal-donation-inline-gallery-item"
style="display:block;border:1px solid rgba(255,255,255,0.2);background:rgba(15,23,42,0.22);padding:3px;border-radius:4px;line-height:0;pointer-events:auto;cursor:pointer;"
>
<img src="<?= Html::encode($galleryUrl) ?>" alt="" style="width:56px;height:56px;object-fit:cover;border-radius:2px;">
</label>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-muted" style="font-size:12px;align-self:center;"><?= Yii::t('DonationsModule.base', 'No gallery images found for this animal.') ?></div>
<?php endif; ?>
</div>
</div>
<?= Html::fileInput('DonationGoalForm[imageFile]', null, [
'id' => $fileFieldId,
'class' => 'js-animal-donation-upload animal-tile-goal-upload-input',
'data-preview' => '#' . $previewId,
'data-editor-bg' => '#' . $quickDonatePanelId,
'accept' => 'image/*',
]) ?>
<label for="<?= Html::encode($fileFieldId) ?>" class="animal-tile-goal-upload-btn">
<?= Yii::t('DonationsModule.base', 'Upload') ?>
</label>
</div>
<?= Html::endForm() ?>
</div>
</div>
<?php endif; ?>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,30 @@ switch ($transfer->status) {
$statusTextColor = '#d1d5db';
break;
}
static $transferActionButtonStylePrinted = false;
if (!$transferActionButtonStylePrinted):
$transferActionButtonStylePrinted = true;
?>
<style>
.animal-transfer-action-btn {
border-radius: 999px;
border: 1px solid rgba(15, 23, 42, 0.24);
background: rgba(255, 255, 255, 0.92);
color: #0f172a !important;
font-weight: 700;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.2);
}
@media (prefers-color-scheme: dark) {
.animal-transfer-action-btn {
border-color: rgba(226, 232, 240, 0.34);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc !important;
}
}
</style>
<?php endif; ?>
?>
<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);">
@@ -69,18 +93,16 @@ switch ($transfer->status) {
Yii::t('AnimalManagementModule.base', 'Accept'),
$space->createUrl('/animal_management/animals/transfer-respond', ['id' => $transfer->id, 'decision' => 'accept']),
[
'class' => 'btn btn-xs btn-default',
'class' => 'btn btn-xs btn-default animal-transfer-action-btn',
'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',
'class' => 'btn btn-xs btn-default animal-transfer-action-btn',
'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): ?>
@@ -88,9 +110,8 @@ switch ($transfer->status) {
Yii::t('AnimalManagementModule.base', 'Complete Transfer'),
$space->createUrl('/animal_management/animals/transfer-complete', ['id' => $transfer->id]),
[
'class' => 'btn btn-xs btn-default',
'class' => 'btn btn-xs btn-default animal-transfer-action-btn',
'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)): ?>
@@ -98,9 +119,8 @@ switch ($transfer->status) {
Yii::t('AnimalManagementModule.base', 'Cancel Request'),
$space->createUrl('/animal_management/animals/transfer-cancel', ['id' => $transfer->id]),
[
'class' => 'btn btn-xs btn-default',
'class' => 'btn btn-xs btn-default animal-transfer-action-btn',
'data-method' => 'post',
'style' => 'border-radius:999px;border:0;background:rgba(255,255,255,0.92);font-weight:600;'
]
) ?>
<?php endif; ?>

View File

@@ -0,0 +1,305 @@
<?php
use humhub\modules\animal_management\models\Animal;
use humhub\modules\animal_management\models\AnimalGalleryItem;
use humhub\modules\space\models\Space;
use yii\helpers\Html;
use yii\helpers\Json;
/* @var Space $space */
/* @var Animal $animal */
/* @var AnimalGalleryItem[] $galleryItems */
/* @var bool $isInline */
/* @var string $returnTo */
/* @var int $maxUploadCount */
/* @var string $errorMessage */
/* @var string $infoMessage */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$returnTo = (string)($returnTo ?? 'view');
$maxUploadCount = max(1, (int)($maxUploadCount ?? 10));
$errorMessage = trim((string)($errorMessage ?? ''));
$infoMessage = trim((string)($infoMessage ?? ''));
$formId = 'animal-gallery-inline-upload-form';
$submitUrl = $space->createUrl('/animal_management/animals/add-gallery-images-inline', [
'id' => (int)$animal->id,
'inline' => $isInline ? 1 : 0,
'returnTo' => $returnTo,
]);
$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);
}
.inline-add-shell > .panel-body {
position: relative;
z-index: 1;
background: rgba(10, 18, 28, 0.2);
}
.inline-add-shell,
.inline-add-shell .panel-body,
.inline-add-shell .control-label,
.inline-add-shell .help-block,
.inline-add-shell h4 {
color: #eef5fb;
}
.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 .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.inline-add-shell .animal-inline-top-save-action:hover,
.inline-add-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
.inline-add-shell .animal-gallery-inline-existing-grid {
margin: 0;
}
.inline-add-shell .animal-gallery-inline-existing-item {
display: block;
width: 100%;
aspect-ratio: 1 / 1;
min-height: 70px;
object-fit: cover;
border-radius: 6px;
}
.inline-add-shell .animal-gallery-inline-existing-wrap {
min-height: 220px;
max-height: 320px;
overflow-y: auto;
overflow-x: hidden;
margin: 6px 0 10px 0;
padding-right: 4px;
}
.inline-add-shell .animal-gallery-inline-file-input {
position: absolute;
left: -10000px;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.inline-add-shell .animal-gallery-inline-file-trigger {
margin: 0;
}
.inline-add-shell .animal-gallery-inline-file-name {
display: inline-block;
margin-left: 8px;
color: rgba(233, 242, 250, 0.9);
font-size: 12px;
vertical-align: middle;
max-width: calc(100% - 150px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
CSS
);
if ($isInline) {
$this->registerCss(<<<CSS
html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
margin-top: 0 !important;
}
body,
.inline-add-shell,
.inline-add-shell > .panel-body,
#{$formId} {
height: 100% !important;
}
#{$formId} {
display: flex;
flex-direction: column;
}
#{$formId} .animal-gallery-inline-existing-wrap {
flex: 1 1 auto;
min-height: 0;
max-height: none;
}
CSS
);
}
?>
<div class="panel panel-default inline-add-shell">
<div class="panel-body">
<form id="<?= Html::encode($formId) ?>" method="post" action="<?= Html::encode($submitUrl) ?>" enctype="multipart/form-data"<?= !$isInline ? ' target="_top"' : '' ?>>
<?= Html::hiddenInput(Yii::$app->request->csrfParam, Yii::$app->request->getCsrfToken()) ?>
<?= Html::hiddenInput('returnTo', $returnTo) ?>
<?php if ($isInline): ?>
<div class="animal-inline-top-actions" style="display:flex;justify-content:flex-end;gap:8px;margin:0 38px 10px 0;">
<?= Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Upload to Gallery'),
'form' => $formId,
]) ?>
<?php if ($showTopCancel): ?>
<?= Html::button('<i class="fa fa-times"></i>', [
'id' => 'gallery-inline-add-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<h4 style="margin:0 0 10px 0;"><?= Yii::t('AnimalManagementModule.base', 'Add Images to Gallery') ?></h4>
<?php if ($errorMessage !== ''): ?>
<div class="alert alert-danger" style="margin-bottom:10px;"><?= Html::encode($errorMessage) ?></div>
<?php endif; ?>
<?php if ($infoMessage !== ''): ?>
<div class="alert alert-info" style="margin-bottom:10px;"><?= Html::encode($infoMessage) ?></div>
<?php endif; ?>
<div class="form-group" style="margin-bottom:8px;">
<label class="control-label" for="galleryImages"><?= Yii::t('AnimalManagementModule.base', 'Upload Images') ?></label>
<div>
<input type="file" class="animal-gallery-inline-file-input" id="galleryImages" name="galleryImages[]" accept="image/*" multiple required>
<label for="galleryImages" class="btn btn-default btn-sm animal-gallery-inline-file-trigger"><?= Yii::t('AnimalManagementModule.base', 'Choose Files') ?></label>
<span id="gallery-inline-file-name" class="animal-gallery-inline-file-name"><?= Yii::t('AnimalManagementModule.base', 'No files selected.') ?></span>
</div>
<div class="help-block" style="margin-bottom:0;"><?= Yii::t('AnimalManagementModule.base', 'You can upload up to {count} images at a time.', ['count' => $maxUploadCount]) ?></div>
</div>
<?php if (!empty($galleryItems)): ?>
<div class="animal-gallery-inline-existing-wrap">
<div class="row animal-gallery-inline-existing-grid">
<?php foreach (array_slice($galleryItems, 0, 12) as $galleryItem): ?>
<?php $galleryUrl = trim((string)$galleryItem->getImageUrl()); ?>
<?php if ($galleryUrl === '') { continue; } ?>
<div class="col-xs-4 col-sm-3" style="margin-bottom:8px;">
<img src="<?= Html::encode($galleryUrl) ?>" alt="<?= Yii::t('AnimalManagementModule.base', 'Gallery image') ?>" class="animal-gallery-inline-existing-item">
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!$isInline): ?>
<button type="submit" class="btn btn-primary"><?= Yii::t('AnimalManagementModule.base', 'Upload to Gallery') ?></button>
<a href="<?= Html::encode($space->createUrl('/animal_management/animals/view', ['id' => $animal->id])) ?>" class="btn btn-default"><?= Yii::t('AnimalManagementModule.base', 'Cancel') ?></a>
<?php endif; ?>
</form>
</div>
</div>
<?php
if ($isInline) {
$cancelPayload = Json::htmlEncode([
'source' => 'animal-inline-editor',
'type' => 'cancel',
'collapseId' => 'gallery-add-inline',
]);
$this->registerJs(<<<JS
(function() {
var fileInput = document.getElementById('galleryImages');
var fileLabel = document.getElementById('gallery-inline-file-name');
function updateFileLabel() {
if (!fileInput || !fileLabel) {
return;
}
var files = fileInput.files;
if (!files || files.length === 0) {
fileLabel.textContent = 'No files selected.';
return;
}
if (files.length === 1) {
fileLabel.textContent = files[0].name || '1 file selected';
return;
}
fileLabel.textContent = files.length + ' files selected';
}
if (fileInput) {
fileInput.addEventListener('change', updateFileLabel);
updateFileLabel();
}
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
if (window.jQuery) {
window.jQuery(document).on('click', '#gallery-inline-add-cancel, #gallery-inline-add-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#gallery-inline-add-cancel, #gallery-inline-add-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}
?>

View File

@@ -17,6 +17,7 @@ use yii\helpers\Json;
/* @var bool $isInline */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$hiddenMedicalKeys = [
'second_physician_name',
@@ -135,6 +136,15 @@ $medicalMediaPath = trim((string)($model->customFields['medical_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';
$visitAtInputValue = trim((string)$model->visit_at);
if ($visitAtInputValue === '') {
$visitAtInputValue = date('Y-m-d\TH:i');
} else {
$visitAtTimestamp = strtotime($visitAtInputValue);
if ($visitAtTimestamp !== false) {
$visitAtInputValue = date('Y-m-d\TH:i', $visitAtTimestamp);
}
}
$this->registerCss(<<<CSS
.inline-add-shell.panel {
@@ -172,6 +182,18 @@ $medicalFormId = 'add-medical-visit-inline-form';
border-color: rgba(255, 255, 255, 0.2);
}
.inline-add-shell .panel.panel-default > .panel-heading a {
color: inherit;
display: block;
text-decoration: none;
}
.inline-add-shell .panel.panel-default > .panel-heading a:hover,
.inline-add-shell .panel.panel-default > .panel-heading a:focus {
color: #ffffff;
text-decoration: none;
}
.inline-add-shell,
.inline-add-shell .panel-body,
.inline-add-shell .control-label,
@@ -204,6 +226,31 @@ $medicalFormId = 'add-medical-visit-inline-form';
.inline-add-shell select.form-control option {
color: #0f1b2a;
}
.inline-add-shell .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.inline-add-shell .animal-inline-top-save-action:hover,
.inline-add-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
CSS
);
@@ -213,6 +260,13 @@ html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
@@ -243,18 +297,20 @@ CSS
<?= 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;">
<div class="animal-inline-top-actions" style="display:flex;justify-content:flex-end;gap:8px;margin:0 38px 10px 0;">
<?= Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'btn btn-default btn-sm',
'class' => 'animal-inline-top-save-action',
'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'),
]) ?>
<?php if ($showTopCancel): ?>
<?= Html::button('<i class="fa fa-times"></i>', [
'id' => 'medical-inline-add-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</div>
<?php endif; ?>
@@ -289,48 +345,77 @@ CSS
</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="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-visit" aria-expanded="false" aria-controls="medical-inline-section-visit">
<strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit') ?></strong>
</a>
</div>
<div id="medical-inline-section-visit" class="panel-collapse collapse" aria-expanded="false">
<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, 'visit_at')->input('datetime-local', ['value' => $visitAtInputValue]) ?></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>
<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="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-vitals" aria-expanded="false" aria-controls="medical-inline-section-vitals">
<strong><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></strong>
</a>
</div>
<div id="medical-inline-section-vitals" class="panel-collapse collapse" aria-expanded="false">
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-conditions" aria-expanded="false" aria-controls="medical-inline-section-conditions">
<strong><?= Yii::t('AnimalManagementModule.base', 'Conditions') ?></strong>
</a>
</div>
<div id="medical-inline-section-conditions" class="panel-collapse collapse" aria-expanded="false">
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-detail" aria-expanded="false" aria-controls="medical-inline-section-detail">
<strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit Detail') ?></strong>
</a>
</div>
<div id="medical-inline-section-detail" class="panel-collapse collapse" aria-expanded="false">
<div class="panel-body" style="padding-bottom:8px;">
<?= $renderCustomField('date_of_most_recent_medical_visit', $model, $customDefinitions) ?>
</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="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-physician" aria-expanded="false" aria-controls="medical-inline-section-physician">
<strong><?= Yii::t('AnimalManagementModule.base', 'Physician') ?></strong>
</a>
</div>
<div id="medical-inline-section-physician" class="panel-collapse collapse" aria-expanded="false">
<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>
@@ -342,6 +427,7 @@ CSS
<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>
@@ -355,23 +441,23 @@ CSS
<?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;">
<div class="panel-heading">
<a data-toggle="collapse" class="collapsed" href="#medical-inline-section-additional" aria-expanded="false" aria-controls="medical-inline-section-additional">
<strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong>
</a>
</div>
<div id="medical-inline-section-additional" class="panel-collapse collapse" aria-expanded="false">
<div class="panel-body" style="padding-bottom:8px;">
<?php foreach ($remainingDefinitions as $fieldKey => $definition): ?>
<?= $renderCustomField($fieldKey, $model, $remainingDefinitions) ?>
<?php endforeach; ?>
</div>
</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: ?>
<?php if (!$isInline): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
->link(($returnTo ?? 'medical-visits') === 'medical-visits'
? $space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])
@@ -507,11 +593,32 @@ if ($isInline) {
]);
$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, '*');
(function() {
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
});
if (window.jQuery) {
window.jQuery(document).on('click', '#medical-inline-add-cancel, #medical-inline-add-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#medical-inline-add-cancel, #medical-inline-add-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}

View File

@@ -17,6 +17,7 @@ use yii\helpers\Json;
/* @var bool $isInline */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$renderCustomField = static function (string $fieldKey, AnimalProgressUpdateForm $formModel, array $definitions): string {
if (!isset($definitions[$fieldKey])) {
@@ -132,6 +133,18 @@ $this->registerCss(<<<CSS
border-color: rgba(255, 255, 255, 0.2);
}
.inline-add-shell .panel.panel-default > .panel-heading a {
color: inherit;
display: block;
text-decoration: none;
}
.inline-add-shell .panel.panel-default > .panel-heading a:hover,
.inline-add-shell .panel.panel-default > .panel-heading a:focus {
color: #ffffff;
text-decoration: none;
}
.inline-add-shell,
.inline-add-shell .panel-body,
.inline-add-shell .control-label,
@@ -164,6 +177,31 @@ $this->registerCss(<<<CSS
.inline-add-shell select.form-control option {
color: #0f1b2a;
}
.inline-add-shell .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.inline-add-shell .animal-inline-top-save-action:hover,
.inline-add-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
CSS
);
@@ -173,6 +211,13 @@ html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
@@ -195,26 +240,33 @@ CSS
<?= Html::hiddenInput('returnTo', (string)($returnTo ?? 'progress-updates')) ?>
<?php if ($isInline): ?>
<div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:10px;">
<div class="animal-inline-top-actions" style="display:flex;justify-content:flex-end;gap:8px;margin:0 38px 10px 0;">
<?= Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'btn btn-default btn-sm',
'class' => 'animal-inline-top-save-action',
'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'),
]) ?>
<?php if ($showTopCancel): ?>
<?= Html::button('<i class="fa fa-times"></i>', [
'id' => 'progress-inline-add-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</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="panel-heading">
<a data-toggle="collapse" href="#progress-inline-section-media" aria-expanded="true" aria-controls="progress-inline-section-media">
<strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong>
</a>
</div>
<div id="progress-inline-section-media" class="panel-collapse collapse in" aria-expanded="true">
<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;">
@@ -239,12 +291,18 @@ CSS
</div>
</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="panel-heading">
<a data-toggle="collapse" href="#progress-inline-section-update" aria-expanded="true" aria-controls="progress-inline-section-update">
<strong><?= Yii::t('AnimalManagementModule.base', 'Progress Update') ?></strong>
</a>
</div>
<div id="progress-inline-section-update" class="panel-collapse collapse in" aria-expanded="true">
<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>
@@ -253,24 +311,37 @@ CSS
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#progress-inline-section-notes" aria-expanded="true" aria-controls="progress-inline-section-notes">
<strong><?= Yii::t('AnimalManagementModule.base', 'Notes') ?></strong>
</a>
</div>
<div id="progress-inline-section-notes" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?= $renderCustomField('progress_notes', $model, $customDefinitions) ?>
<?= $renderCustomField('routine_updates', $model, $customDefinitions) ?>
</div>
</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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#progress-inline-section-additional" aria-expanded="true" aria-controls="progress-inline-section-additional">
<strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong>
</a>
</div>
<div id="progress-inline-section-additional" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?php foreach ($otherCustomDefinitions as $fieldKey => $definition): ?>
<?= $renderCustomField($fieldKey, $model, $otherCustomDefinitions) ?>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
@@ -316,13 +387,7 @@ CSS
</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: ?>
<?php if (!$isInline): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
->link(($returnTo ?? 'progress-updates') === 'progress-updates'
? $space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])
@@ -434,11 +499,32 @@ if ($isInline) {
]);
$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, '*');
(function() {
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
});
if (window.jQuery) {
window.jQuery(document).on('click', '#progress-inline-add-cancel, #progress-inline-add-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#progress-inline-add-cancel, #progress-inline-add-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}

View File

@@ -13,6 +13,18 @@ use yii\helpers\Html;
/* @var Space $space */
/* @var bool $isEdit */
/* @var Animal|null $animal */
/* @var bool $showIntakeGoalSection */
$showIntakeGoalSection = isset($showIntakeGoalSection) ? (bool)$showIntakeGoalSection : false;
$intakeGoalInput = Yii::$app->request->post('IntakeGoal', []);
if (!is_array($intakeGoalInput)) {
$intakeGoalInput = [];
}
$intakeGoalEnabled = !empty($isEdit) ? false : ((int)($intakeGoalInput['enabled'] ?? 0) === 1);
$intakeGoalTitle = trim((string)($intakeGoalInput['title'] ?? ''));
$intakeGoalTargetAmount = trim((string)($intakeGoalInput['target_amount'] ?? ''));
$intakeGoalDescription = trim((string)($intakeGoalInput['description'] ?? ''));
$intakeGoalIsActive = !empty($isEdit) ? true : ((int)($intakeGoalInput['is_active'] ?? 1) === 1);
?>
<div class="panel panel-default">
@@ -215,6 +227,41 @@ use yii\helpers\Html;
</div>
<div class="col-md-6">
<?php if (empty($isEdit) && $showIntakeGoalSection): ?>
<div class="panel panel-default" style="margin-bottom:14px;">
<div class="panel-heading"><strong><?= Yii::t('AnimalManagementModule.base', 'Intake Donation Goal') ?></strong></div>
<div class="panel-body" style="padding-bottom:8px;">
<div class="checkbox" style="margin-top:0;">
<label>
<input type="checkbox" name="IntakeGoal[enabled]" value="1" id="intake-goal-enabled" <?= $intakeGoalEnabled ? 'checked' : '' ?>>
<?= Yii::t('AnimalManagementModule.base', 'Create a donation goal for this animal on intake') ?>
</label>
</div>
<div id="intake-goal-fields" style="display:<?= $intakeGoalEnabled ? 'block' : 'none' ?>;">
<div class="form-group">
<label class="control-label" for="intake-goal-title"><?= Yii::t('AnimalManagementModule.base', 'Goal Title') ?></label>
<input type="text" id="intake-goal-title" name="IntakeGoal[title]" class="form-control" maxlength="190" value="<?= Html::encode($intakeGoalTitle) ?>" placeholder="<?= Html::encode(Yii::t('AnimalManagementModule.base', 'Example: {animalName} Care Fund', ['animalName' => 'Animal'])) ?>">
</div>
<div class="form-group">
<label class="control-label" for="intake-goal-target"><?= Yii::t('AnimalManagementModule.base', 'Target Amount') ?></label>
<input type="number" id="intake-goal-target" name="IntakeGoal[target_amount]" class="form-control" min="1" step="1" value="<?= Html::encode($intakeGoalTargetAmount) ?>" placeholder="1500">
</div>
<div class="form-group">
<label class="control-label" for="intake-goal-description"><?= Yii::t('AnimalManagementModule.base', 'Goal Description') ?></label>
<textarea id="intake-goal-description" name="IntakeGoal[description]" class="form-control" rows="3" placeholder="<?= Html::encode(Yii::t('AnimalManagementModule.base', 'Optional context for supporters.')) ?>"><?= Html::encode($intakeGoalDescription) ?></textarea>
</div>
<div class="checkbox" style="margin-bottom:0;">
<label>
<input type="hidden" name="IntakeGoal[is_active]" value="0">
<input type="checkbox" name="IntakeGoal[is_active]" value="1" <?= $intakeGoalIsActive ? 'checked' : '' ?>>
<?= Yii::t('AnimalManagementModule.base', 'Goal is active immediately') ?>
</label>
</div>
</div>
</div>
</div>
<?php endif; ?>
<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;">
@@ -274,7 +321,7 @@ use yii\helpers\Html;
<?= Button::save(!empty($isEdit)
? Yii::t('AnimalManagementModule.base', 'Save Changes')
: Yii::t('AnimalManagementModule.base', 'Create Animal'))->submit() ?>
: Yii::t('AnimalManagementModule.base', 'Complete Intake'))->submit() ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))->link(
!empty($isEdit) && $animal instanceof Animal
? $space->createUrl('/animal_management/animals/view', ['id' => $animal->id])
@@ -306,7 +353,7 @@ use yii\helpers\Html;
<?= 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'),
: Yii::t('AnimalManagementModule.base', 'Complete Intake'),
]) ?>
</div>
<?php if (!empty($galleryImageUrls)): ?>
@@ -350,7 +397,7 @@ use yii\helpers\Html;
<?= 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'),
: Yii::t('AnimalManagementModule.base', 'Complete Intake'),
]) ?>
</div>
<?php if (!empty($galleryImageUrls)): ?>
@@ -521,6 +568,14 @@ $this->registerJs(<<<JS
}
});
$('#intake-goal-enabled').on('change', function() {
if ($(this).is(':checked')) {
$('#intake-goal-fields').slideDown(120);
} else {
$('#intake-goal-fields').slideUp(120);
}
});
$('#animal-cover-image-manage-modal').on('shown.bs.modal', function() {
markSelectedThumb('cover', $('#animalform-coverimagegallerypath').val());
});

View File

@@ -19,6 +19,7 @@ use yii\helpers\Json;
/* @var bool $isInline */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$hiddenMedicalKeys = [
'second_physician_name',
@@ -166,6 +167,18 @@ $this->registerCss(<<<CSS
border-color: rgba(255, 255, 255, 0.2);
}
.inline-editor-shell .panel.panel-default > .panel-heading a {
color: inherit;
display: block;
text-decoration: none;
}
.inline-editor-shell .panel.panel-default > .panel-heading a:hover,
.inline-editor-shell .panel.panel-default > .panel-heading a:focus {
color: #ffffff;
text-decoration: none;
}
.inline-editor-shell,
.inline-editor-shell .panel-body,
.inline-editor-shell .control-label,
@@ -198,6 +211,31 @@ $this->registerCss(<<<CSS
.inline-editor-shell select.form-control option {
color: #0f1b2a;
}
.inline-editor-shell .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.inline-editor-shell .animal-inline-top-save-action:hover,
.inline-editor-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
CSS
);
@@ -207,6 +245,13 @@ html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
@@ -229,18 +274,20 @@ CSS
<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;">
<span class="animal-inline-top-actions" style="display:inline-flex;gap:8px;margin-right:38px;">
<?= Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'btn btn-default btn-sm',
'class' => 'animal-inline-top-save-action',
'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'),
]) ?>
<?php if ($showTopCancel): ?>
<?= Html::button('<i class="fa fa-times"></i>', [
'id' => 'medical-inline-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</span>
<?php endif; ?>
</div>
@@ -259,48 +306,77 @@ CSS
<?= $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="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-visit" aria-expanded="true" aria-controls="edit-medical-section-visit">
<strong><?= Yii::t('AnimalManagementModule.base', 'Visit') ?></strong>
</a>
</div>
<div id="edit-medical-section-visit" class="panel-collapse collapse in" aria-expanded="true">
<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>
<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="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-vitals" aria-expanded="true" aria-controls="edit-medical-section-vitals">
<strong><?= Yii::t('AnimalManagementModule.base', 'Vitals') ?></strong>
</a>
</div>
<div id="edit-medical-section-vitals" class="panel-collapse collapse in" aria-expanded="true">
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-conditions" aria-expanded="true" aria-controls="edit-medical-section-conditions">
<strong><?= Yii::t('AnimalManagementModule.base', 'Conditions') ?></strong>
</a>
</div>
<div id="edit-medical-section-conditions" class="panel-collapse collapse in" aria-expanded="true">
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-detail" aria-expanded="true" aria-controls="edit-medical-section-detail">
<strong><?= Yii::t('AnimalManagementModule.base', 'Medical Visit Detail') ?></strong>
</a>
</div>
<div id="edit-medical-section-detail" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?= $renderCustomField('date_of_most_recent_medical_visit', $model, $customDefinitions) ?>
</div>
</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="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-media" aria-expanded="true" aria-controls="edit-medical-section-media">
<strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong>
</a>
</div>
<div id="edit-medical-section-media" class="panel-collapse collapse in" aria-expanded="true">
<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;">
@@ -323,12 +399,18 @@ CSS
</div>
</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="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-physician" aria-expanded="true" aria-controls="edit-medical-section-physician">
<strong><?= Yii::t('AnimalManagementModule.base', 'Physician') ?></strong>
</a>
</div>
<div id="edit-medical-section-physician" class="panel-collapse collapse in" aria-expanded="true">
<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>
@@ -340,6 +422,7 @@ CSS
<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>
@@ -353,23 +436,23 @@ CSS
<?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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-medical-section-additional" aria-expanded="true" aria-controls="edit-medical-section-additional">
<strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong>
</a>
</div>
<div id="edit-medical-section-additional" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?php foreach ($remainingDefinitions as $fieldKey => $definition): ?>
<?= $renderCustomField($fieldKey, $model, $remainingDefinitions) ?>
<?php endforeach; ?>
</div>
</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: ?>
<?php if (!$isInline): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
->link(($returnTo ?? 'view') === 'medical-visits'
? $space->createUrl('/animal_management/animals/medical-visits', ['id' => $animal->id])
@@ -486,11 +569,32 @@ if ($isInline) {
]);
$this->registerJs(<<<JS
$(document).on('click', '#medical-inline-cancel, #medical-inline-cancel-icon', function() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
(function() {
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
});
if (window.jQuery) {
window.jQuery(document).on('click', '#medical-inline-cancel, #medical-inline-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#medical-inline-cancel, #medical-inline-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}

View File

@@ -19,6 +19,7 @@ use yii\helpers\Json;
/* @var bool $isInline */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$renderCustomField = static function (string $fieldKey, AnimalProgressUpdateForm $formModel, array $definitions): string {
if (!isset($definitions[$fieldKey])) {
@@ -126,6 +127,18 @@ $this->registerCss(<<<CSS
border-color: rgba(255, 255, 255, 0.2);
}
.inline-editor-shell .panel.panel-default > .panel-heading a {
color: inherit;
display: block;
text-decoration: none;
}
.inline-editor-shell .panel.panel-default > .panel-heading a:hover,
.inline-editor-shell .panel.panel-default > .panel-heading a:focus {
color: #ffffff;
text-decoration: none;
}
.inline-editor-shell,
.inline-editor-shell .panel-body,
.inline-editor-shell .control-label,
@@ -158,6 +171,31 @@ $this->registerCss(<<<CSS
.inline-editor-shell select.form-control option {
color: #0f1b2a;
}
.inline-editor-shell .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.inline-editor-shell .animal-inline-top-save-action:hover,
.inline-editor-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
CSS
);
@@ -167,6 +205,13 @@ html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
@@ -182,18 +227,20 @@ CSS
<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;">
<span class="animal-inline-top-actions" style="display:inline-flex;gap:8px;margin-right:38px;">
<?= Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'btn btn-default btn-sm',
'class' => 'animal-inline-top-save-action',
'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'),
]) ?>
<?php if ($showTopCancel): ?>
<?= Html::button('<i class="fa fa-times"></i>', [
'id' => 'progress-inline-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</span>
<?php endif; ?>
</div>
@@ -211,8 +258,13 @@ CSS
<?= $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="panel-heading">
<a data-toggle="collapse" href="#edit-progress-section-update" aria-expanded="true" aria-controls="edit-progress-section-update">
<strong><?= Yii::t('AnimalManagementModule.base', 'Progress Update') ?></strong>
</a>
</div>
<div id="edit-progress-section-update" class="panel-collapse collapse in" aria-expanded="true">
<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>
@@ -221,31 +273,49 @@ CSS
<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>
<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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-progress-section-notes" aria-expanded="true" aria-controls="edit-progress-section-notes">
<strong><?= Yii::t('AnimalManagementModule.base', 'Notes') ?></strong>
</a>
</div>
<div id="edit-progress-section-notes" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?= $renderCustomField('progress_notes', $model, $customDefinitions) ?>
<?= $renderCustomField('routine_updates', $model, $customDefinitions) ?>
</div>
</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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-progress-section-additional" aria-expanded="true" aria-controls="edit-progress-section-additional">
<strong><?= Yii::t('AnimalManagementModule.base', 'Additional Details') ?></strong>
</a>
</div>
<div id="edit-progress-section-additional" class="panel-collapse collapse in" aria-expanded="true">
<div class="panel-body" style="padding-bottom:8px;">
<?php foreach ($otherCustomDefinitions as $fieldKey => $definition): ?>
<?= $renderCustomField($fieldKey, $model, $otherCustomDefinitions) ?>
<?php endforeach; ?>
</div>
</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;">
<div class="panel-heading">
<a data-toggle="collapse" href="#edit-progress-section-media" aria-expanded="true" aria-controls="edit-progress-section-media">
<strong><?= Yii::t('AnimalManagementModule.base', 'Media') ?></strong>
</a>
</div>
<div id="edit-progress-section-media" class="panel-collapse collapse in" aria-expanded="true">
<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;">
@@ -270,6 +340,7 @@ CSS
</div>
</div>
</div>
</div>
</div>
</div>
@@ -314,13 +385,7 @@ CSS
</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: ?>
<?php if (!$isInline): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
->link(($returnTo ?? 'view') === 'progress-updates'
? $space->createUrl('/animal_management/animals/progress-updates', ['id' => $animal->id])
@@ -413,11 +478,32 @@ if ($isInline) {
]);
$this->registerJs(<<<JS
$(document).on('click', '#progress-inline-cancel, #progress-inline-cancel-icon', function() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
(function() {
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
});
if (window.jQuery) {
window.jQuery(document).on('click', '#progress-inline-cancel, #progress-inline-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#progress-inline-cancel, #progress-inline-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}

View File

@@ -23,11 +23,14 @@ use yii\helpers\Html;
/* @var array<int, AnimalMedicalVisit> $latestMedicalVisitByAnimal */
/* @var array<int, string> $animalImageUrls */
/* @var array<int, string> $transferAnimalImageUrls */
/* @var array<int, mixed> $animalDonationGoalsByAnimal */
/* @var array $tileFields */
/* @var array<int, array> $tileFieldOverrides */
/* @var Space $space */
/* @var bool $canManage */
$isTabletFocusMode = ($viewMode === 'tablet') && ((string)Yii::$app->request->get('focus', '') === '1');
$currentParams = [
'q' => $queryValue,
'status' => $statusFilter,
@@ -40,6 +43,13 @@ $currentParams = [
$buildUrl = static function (array $overrides) use ($space, $currentParams): string {
$params = array_merge($currentParams, $overrides);
if (($params['view'] ?? '') !== 'tablet') {
unset($params['focus']);
} elseif (($params['focus'] ?? '') !== '1') {
unset($params['focus']);
}
return $space->createUrl('/animal_management/animals/index', $params);
};
@@ -47,16 +57,70 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
$nextDirection = ($sortKey === $column && $sortDirection === 'asc') ? 'desc' : 'asc';
return $buildUrl(['sort' => $column, 'direction' => $nextDirection, 'view' => 'table']);
};
$showDonationSettingsLinks = $canManage && $space->moduleManager->isEnabled('donations');
?>
<div class="panel panel-default">
<?php if ($isTabletFocusMode): ?>
<style>
html,
body {
overflow: hidden !important;
}
#animals-index-panel.animal-focus-overlay {
position: fixed !important;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
margin: 0 !important;
border-radius: 0 !important;
border: 0 !important;
width: 100vw !important;
max-width: 100vw !important;
height: 100vh !important;
display: flex;
flex-direction: column;
background: #fff;
}
#animals-index-panel.animal-focus-overlay .panel-heading {
flex: 0 0 auto;
}
#animals-index-panel.animal-focus-overlay .panel-body {
flex: 1 1 auto;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
</style>
<?php endif; ?>
<div id="animals-index-panel" class="panel panel-default<?= $isTabletFocusMode ? ' animal-focus-overlay' : '' ?>">
<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']), [
<?= Html::a('<i class="fa fa-tablet"></i>', $buildUrl(['view' => 'tablet', 'focus' => '1']), [
'class' => 'btn btn-default btn-sm' . ($viewMode === 'tablet' ? ' active' : ''),
'title' => Yii::t('AnimalManagementModule.base', 'Tablet View'),
'aria-label' => Yii::t('AnimalManagementModule.base', 'Tablet View'),
]) ?>
<?= Html::a('<i class="fa fa-bars"></i>', $buildUrl(['view' => 'rows']), [
'class' => 'btn btn-default btn-sm' . ($viewMode === 'rows' ? ' active' : ''),
'title' => Yii::t('AnimalManagementModule.base', 'Row View'),
'aria-label' => Yii::t('AnimalManagementModule.base', 'Row View'),
]) ?>
<?= Html::a('<i class="fa fa-columns"></i>', $buildUrl(['view' => 'tiles2']), [
'class' => 'btn btn-default btn-sm' . ($viewMode === 'tiles2' ? ' active' : ''),
'title' => Yii::t('AnimalManagementModule.base', '2-Column View'),
'aria-label' => Yii::t('AnimalManagementModule.base', '2-Column View'),
]) ?>
<?= Html::a('<i class="fa fa-th"></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'),
'title' => Yii::t('AnimalManagementModule.base', '3-Column View'),
'aria-label' => Yii::t('AnimalManagementModule.base', '3-Column View'),
]) ?>
<?= Html::a('<i class="fa fa-table"></i>', $buildUrl(['view' => 'table']), [
'class' => 'btn btn-default btn-sm' . ($viewMode === 'table' ? ' active' : ''),
@@ -76,6 +140,9 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
<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) ?>">
<?php if ($isTabletFocusMode): ?>
<input type="hidden" name="focus" value="1">
<?php endif; ?>
<input type="hidden" name="sort" value="<?= Html::encode($sortKey) ?>">
<input type="hidden" name="direction" value="<?= Html::encode($sortDirection) ?>">
<?php foreach ($selectedColumns as $col): ?>
@@ -125,12 +192,22 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
<?= Yii::t('AnimalManagementModule.base', 'No animal profiles yet. Create the first intake record to begin tracking.') ?>
</div>
<?php else: ?>
<?php if ($viewMode === 'tiles'): ?>
<?php if ($viewMode !== 'table'): ?>
<?php
$tileColumnClass = 'col-xs-12 col-sm-6 col-md-4';
if ($viewMode === 'tiles2') {
$tileColumnClass = 'col-xs-12 col-sm-6';
} elseif ($viewMode === 'rows') {
$tileColumnClass = 'col-xs-12';
} elseif ($viewMode === 'tablet') {
$tileColumnClass = 'col-xs-12';
}
?>
<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;">
<div class="<?= Html::encode($tileColumnClass) ?>" style="margin-bottom:16px;">
<?= $this->render('_tile', [
'animal' => $animal,
'contentContainer' => $space,
@@ -138,6 +215,9 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
'imageUrl' => $animalImageUrls[$animalId] ?? '',
'tileFields' => $tileFieldOverrides[$animalId] ?? $tileFields,
'showMedicalIcon' => true,
'showDonationSettingsButton' => $showDonationSettingsLinks,
'existingDonationGoal' => $animalDonationGoalsByAnimal[$animalId] ?? null,
'tileLayoutMode' => $viewMode,
]) ?>
</div>
<?php endforeach; ?>
@@ -205,6 +285,13 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
->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 ($showDonationSettingsLinks): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Donations'))
->link($space->createUrl('/donations/settings', [
'goalType' => 'animal',
'targetAnimalId' => (int)$animal->id,
])) ?>
<?php endif; ?>
<?php if ($canManage): ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Edit'))
->link($space->createUrl('/animal_management/animals/edit', ['id' => $animal->id])) ?>
@@ -223,10 +310,11 @@ $sortUrl = static function (string $column) use ($buildUrl, $sortKey, $sortDirec
<?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: ?>
<?php $incomingTransfersHeading = empty($incomingTransfers)
? Yii::t('AnimalManagementModule.base', 'No Incoming Transfers')
: Yii::t('AnimalManagementModule.base', 'Incoming Transfer Requests'); ?>
<h4 id="incoming-transfers" style="margin-top:0;"><?= Html::encode($incomingTransfersHeading) ?></h4>
<?php if (!empty($incomingTransfers)): ?>
<div class="row" style="margin-bottom:4px;">
<?php foreach ($incomingTransfers as $transfer): ?>
<?php

View File

@@ -15,7 +15,7 @@ $payload = [
$jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
?>
<div class="panel panel-success" style="margin:10px;">
<div class="panel panel-success" data-animal-inline-result="saved" data-animal-inline-collapse-id="<?= Html::encode((string)$collapseId) ?>" style="margin:10px;">
<div class="panel-body" style="padding:12px;">
<?= Html::encode(Yii::t('AnimalManagementModule.base', 'Saved. Updating section...')) ?>
</div>
@@ -25,8 +25,59 @@ $jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNI
$this->registerJs(<<<JS
(function() {
var payload = {$jsonPayload};
function triggerChange(element) {
if (!element) {
return;
}
var event;
if (typeof Event === 'function') {
event = new Event('change', { bubbles: true });
} else {
event = document.createEvent('Event');
event.initEvent('change', true, true);
}
element.dispatchEvent(event);
}
function closeByFrameContext() {
if (!window.parent || window.parent === window) {
return;
}
var frameElement = window.frameElement;
if (!frameElement || !frameElement.parentElement) {
return;
}
var closeControl = frameElement.parentElement.querySelector('.animal-tile-inline-iframe-close[for]');
if (!closeControl) {
return;
}
var targetId = String(closeControl.getAttribute('for') || '');
if (!targetId) {
return;
}
var targetElement = window.parent.document.getElementById(targetId);
if (!targetElement) {
return;
}
if ((targetElement.type || '').toLowerCase() === 'radio') {
targetElement.checked = true;
} else {
targetElement.checked = false;
}
triggerChange(targetElement);
}
if (window.parent && window.parent !== window) {
window.parent.postMessage(payload, '*');
closeByFrameContext();
}
})();
JS

View File

@@ -5,27 +5,205 @@ use humhub\modules\animal_management\models\forms\TransferRequestForm;
use humhub\modules\space\models\Space;
use humhub\widgets\Button;
use yii\bootstrap\ActiveForm;
use yii\helpers\Json;
/* @var Space $space */
/* @var Animal $animal */
/* @var TransferRequestForm $model */
/* @var bool $isInline */
/* @var string $returnTo */
$isInline = isset($isInline) ? (bool)$isInline : false;
$showTopCancel = (string)Yii::$app->request->get('showTopCancel', '0') === '1';
$returnTo = (string)($returnTo ?? 'view');
$transferFormId = 'animal-transfer-request-form';
$this->registerCss(<<<CSS
.animal-transfer-inline-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);
}
.animal-transfer-inline-shell > .panel-body {
position: relative;
z-index: 1;
background: rgba(10, 18, 28, 0.2);
}
.animal-transfer-inline-shell,
.animal-transfer-inline-shell .panel-body,
.animal-transfer-inline-shell .panel-heading,
.animal-transfer-inline-shell .control-label,
.animal-transfer-inline-shell .help-block {
color: #eef5fb;
}
.animal-transfer-inline-shell .panel-heading {
background: rgba(15, 23, 42, 0.4);
border-color: rgba(255, 255, 255, 0.22);
margin-left: 20px;
margin-bottom: 20px;
padding: 10px 20px;
max-width: fit-content;
border-radius: 10px;
}
.animal-transfer-inline-shell .panel-heading strong {
color: #f8fafc;
}
.animal-transfer-inline-shell .form-control {
background: rgba(10, 18, 28, 0.56);
border-color: rgba(255, 255, 255, 0.44);
color: #f3f8ff;
}
.animal-transfer-inline-shell .form-control::placeholder {
color: rgba(243, 248, 255, 0.72);
}
.animal-transfer-inline-shell select.form-control option {
color: #0f1b2a;
}
.animal-transfer-inline-shell .animal-inline-top-save-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
padding: 0;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.36);
background: rgba(15, 23, 42, 0.72);
color: #f8fafc;
cursor: pointer;
line-height: 1;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.animal-transfer-inline-shell .animal-inline-top-save-action:hover,
.animal-transfer-inline-shell .animal-inline-top-save-action:focus {
background: rgba(30, 41, 59, 0.86);
border-color: rgba(255, 255, 255, 0.5);
color: #f8fafc;
outline: none;
}
CSS
);
if ($isInline) {
$this->registerCss(<<<CSS
html, body {
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
scrollbar-width: none;
-ms-overflow-style: none;
}
html::-webkit-scrollbar, body::-webkit-scrollbar {
width: 0;
height: 0;
}
body > .panel:first-child {
margin-top: 0 !important;
}
CSS
);
}
?>
<div class="panel panel-default">
<div class="panel panel-default animal-transfer-inline-shell">
<?php if ($isInline): ?>
<div class="animal-inline-top-actions" style="display:flex;justify-content:flex-end;gap:8px;margin:8px 38px 6px 0;">
<?= \yii\helpers\Html::submitButton('<i class="fa fa-check"></i>', [
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Send Request'),
'form' => $transferFormId,
]) ?>
<?php if ($showTopCancel): ?>
<?= \yii\helpers\Html::button('<i class="fa fa-times"></i>', [
'id' => 'transfer-inline-add-cancel-icon',
'class' => 'animal-inline-top-save-action',
'title' => Yii::t('AnimalManagementModule.base', 'Cancel'),
'type' => 'button',
]) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<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(); ?>
<?php
$formOptions = ['id' => $transferFormId];
if (!$isInline) {
$formOptions['target'] = '_top';
}
$form = ActiveForm::begin(['options' => $formOptions]);
?>
<?= \yii\helpers\Html::hiddenInput('returnTo', $returnTo) ?>
<?= $form->errorSummary($model, ['showAllErrors' => true]) ?>
<?= $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 if (!$isInline): ?>
<?= Button::save(Yii::t('AnimalManagementModule.base', 'Send Request'))->submit() ?>
<?= Button::asLink(Yii::t('AnimalManagementModule.base', 'Cancel'))
->link($space->createUrl('/animal_management/animals/index')) ?>
<?php endif; ?>
<?php ActiveForm::end(); ?>
</div>
</div>
<?php
if ($isInline) {
$cancelPayload = Json::htmlEncode([
'source' => 'animal-inline-editor',
'type' => 'cancel',
'collapseId' => 'transfer-add-inline',
]);
$this->registerJs(<<<JS
(function() {
function postInlineCancel() {
if (window.parent && window.parent !== window) {
window.parent.postMessage($cancelPayload, '*');
}
}
if (window.jQuery) {
window.jQuery(document).on('click', '#transfer-inline-add-cancel, #transfer-inline-add-cancel-icon', function() {
postInlineCancel();
});
}
document.addEventListener('click', function(event) {
var target = event.target;
if (!target || !target.closest) {
return;
}
if (!target.closest('#transfer-inline-add-cancel, #transfer-inline-add-cancel-icon')) {
return;
}
postInlineCancel();
}, false);
})();
JS
, \yii\web\View::POS_END);
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
use humhub\modules\animal_management\models\forms\DisplaySettingsForm;
use humhub\modules\animal_management\models\forms\FieldDefinitionSettingsForm;
use humhub\modules\space\models\Space;
use humhub\widgets\Button;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
@@ -10,6 +11,7 @@ use yii\helpers\Html;
/* @var int $animalCount */
/* @var FieldDefinitionSettingsForm $fieldSettingsForm */
/* @var DisplaySettingsForm $displaySettingsForm */
/* @var Space $space */
?>
<div class="panel panel-default">
@@ -20,6 +22,25 @@ use yii\helpers\Html;
<?php endif; ?>
<div class="panel-body">
<div class="well well-sm" style="margin-bottom:14px;">
<div style="font-weight:700;margin-bottom:6px;"><?= Yii::t('AnimalManagementModule.base', 'Module Setup') ?></div>
<div style="margin-bottom:8px;">
<?= Yii::t('AnimalManagementModule.base', 'Animal Management provides intake, profile tracking, medical/progress records, transfers, and related feed integrations for this rescue space.') ?>
</div>
<div style="margin-bottom:10px;">
<?= Yii::t('AnimalManagementModule.base', 'Run setup to apply pending Animal Management migrations and initialize default module settings for this space.') ?>
</div>
<?= Html::a(
Yii::t('AnimalManagementModule.base', 'Run Animal Management Setup'),
$space->createUrl('/animal_management/settings/setup'),
[
'class' => 'btn btn-primary btn-sm',
'data-method' => 'post',
'data-confirm' => Yii::t('AnimalManagementModule.base', 'Run Animal Management setup now for this space?'),
]
) ?>
</div>
<p><?= Yii::t('AnimalManagementModule.base', 'Configure intake/profile field definitions used by Animal Management.') ?></p>
<div class="well well-sm" style="margin-bottom:12px;">

View File

@@ -7,15 +7,17 @@ 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\animal_management\permissions\ManageAnimals;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
use humhub\modules\space\models\Space;
use Yii;
use yii\base\Widget;
class SearchAnimalProfilesBlock extends Widget
{
public ContentContainerActiveRecord $contentContainer;
public int $limit = 10;
public int $limit = 4;
public function run()
{
@@ -25,29 +27,19 @@ class SearchAnimalProfilesBlock extends Widget
. '</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();
$perPage = max(1, $this->limit);
$page = (int)Yii::$app->request->get('animalFeedPage', 1);
if ($page < 1) {
$page = 1;
}
$totalCount = (int)$query->count();
$displayCount = $showAll ? $totalCount : min($countParam, $totalCount);
$pageCount = max(1, (int)ceil($totalCount / $perPage));
if ($page > $pageCount) {
$page = $pageCount;
}
$offset = ($page - 1) * $perPage;
$settings = Yii::$app->getModule('animal_management')->settings->contentContainer($this->contentContainer);
$heading = trim((string)$settings->get('searchBlockHeading', DisplaySettingsForm::DEFAULT_SEARCH_BLOCK_HEADING));
@@ -60,7 +52,8 @@ class SearchAnimalProfilesBlock extends Widget
$animals = $query
->orderBy(['updated_at' => SORT_DESC, 'id' => SORT_DESC])
->limit($displayCount)
->offset($offset)
->limit($perPage)
->all();
$animalIds = array_map(static function (Animal $animal): int {
@@ -70,9 +63,14 @@ class SearchAnimalProfilesBlock extends Widget
$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');
$animalDonationGoalsByAnimal = $this->resolveAnimalDonationGoals($animalIds);
$hasMore = !$showAll && $displayCount < $totalCount;
$nextCount = min($displayCount + $this->limit, $totalCount);
$canManageAnimals = $this->contentContainer->can(ManageAnimals::class)
|| ($this->contentContainer instanceof Space && $this->contentContainer->isAdmin());
$showDonationSettingsButton = $canManageAnimals && $this->contentContainer->moduleManager->isEnabled('donations');
$hasPreviousPage = $page > 1;
$hasNextPage = $page < $pageCount;
$viewFile = $this->getViewPath() . '/searchAnimalProfilesBlock.php';
if (!is_file($viewFile)) {
@@ -84,20 +82,59 @@ class SearchAnimalProfilesBlock extends Widget
return $this->render('searchAnimalProfilesBlock', [
'animals' => $animals,
'contentContainer' => $this->contentContainer,
'queryValue' => $queryValue,
'heading' => $heading,
'tileFields' => $tileFields,
'tileFieldOverrides' => $tileFieldOverrides,
'latestMedicalVisitByAnimal' => $latestMedicalVisitByAnimal,
'animalImageUrls' => $animalImageUrls,
'animalDonationGoalsByAnimal' => $animalDonationGoalsByAnimal,
'showDonationSettingsButton' => $showDonationSettingsButton,
'totalCount' => $totalCount,
'displayCount' => $displayCount,
'hasMore' => $hasMore,
'nextCount' => $nextCount,
'showAll' => $showAll,
'page' => $page,
'pageCount' => $pageCount,
'hasPreviousPage' => $hasPreviousPage,
'hasNextPage' => $hasNextPage,
]);
}
private function resolveAnimalDonationGoals(array $animalIds): array
{
$animalIds = array_values(array_unique(array_map('intval', $animalIds)));
if (empty($animalIds)
|| !$this->contentContainer->moduleManager->isEnabled('donations')
) {
return [];
}
$donationGoalClass = 'humhub\\modules\\donations\\models\\DonationGoal';
if (!class_exists($donationGoalClass)
|| Yii::$app->db->schema->getTableSchema($donationGoalClass::tableName(), true) === null
) {
return [];
}
$goals = $donationGoalClass::find()
->where([
'contentcontainer_id' => $this->contentContainer->contentcontainer_id,
'goal_type' => $donationGoalClass::TYPE_ANIMAL,
])
->andWhere(['target_animal_id' => $animalIds])
->orderBy(['is_active' => SORT_DESC, 'id' => SORT_DESC])
->all();
$result = [];
foreach ($goals as $goal) {
$animalId = (int)$goal->target_animal_id;
if ($animalId <= 0 || isset($result[$animalId])) {
continue;
}
$result[$animalId] = $goal;
}
return $result;
}
private function normalizeDisplayFields($raw, array $default): array
{
if (is_string($raw)) {

View File

@@ -7,46 +7,30 @@ 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 array<int, mixed> $animalDonationGoalsByAnimal */
/* @var bool $showDonationSettingsButton */
/* @var int $totalCount */
/* @var int $displayCount */
/* @var bool $hasMore */
/* @var int $nextCount */
/* @var bool $showAll */
/* @var int $page */
/* @var int $pageCount */
/* @var bool $hasPreviousPage */
/* @var bool $hasNextPage */
$moduleEnabled = $contentContainer->moduleManager->isEnabled('animal_management');
$allAnimalsUrl = $contentContainer->createUrl('/animal_management/animals/index');
$currentParams = Yii::$app->request->getQueryParams();
$buildProfileUrl = static function (array $overrides) use ($contentContainer, $currentParams): string {
unset($currentParams['q'], $currentParams['animalFeedCount'], $currentParams['animalFeedAll']);
$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: ?>
<?php if (!$moduleEnabled): ?>
<div class="well well-sm" style="margin-bottom:12px;">
<?= Yii::t('AnimalManagementModule.base', 'Animal profiles are not enabled for this rescue.') ?>
</div>
@@ -68,28 +52,36 @@ $buildProfileUrl = static function (array $overrides) use ($contentContainer, $c
'imageUrl' => $animalImageUrls[$animalId] ?? '',
'tileFields' => $tileFieldOverrides[$animalId] ?? $tileFields,
'showMedicalIcon' => true,
'showDonationSettingsButton' => $showDonationSettingsButton,
'existingDonationGoal' => $animalDonationGoalsByAnimal[$animalId] ?? null,
'tileLayoutMode' => 'rows',
]) ?>
</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; ?>
<?php if ($moduleEnabled && $totalCount > 0): ?>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;">
<div class="text-muted" style="font-size:12px;">
<?= Yii::t('AnimalManagementModule.base', 'Page {page} of {pages}', ['page' => $page, 'pages' => $pageCount]) ?>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<?php if ($hasPreviousPage): ?>
<a class="btn btn-default btn-sm" href="<?= Html::encode($buildProfileUrl(['animalFeedPage' => $page - 1])) ?>">
<?= Yii::t('AnimalManagementModule.base', 'Previous') ?>
</a>
<?php endif; ?>
<?php if ($hasNextPage): ?>
<a class="btn btn-default btn-sm" href="<?= Html::encode($buildProfileUrl(['animalFeedPage' => $page + 1])) ?>">
<?= Yii::t('AnimalManagementModule.base', 'Next') ?>
</a>
<?php endif; ?>
</div>
</div>
<div style="margin-top:10px;text-align:center;">
<a href="<?= Html::encode($allAnimalsUrl) ?>" style="font-weight:700;text-decoration:underline;">
<?= Yii::t('AnimalManagementModule.base', 'All Animals') ?>
</a>
</div>
<?php endif; ?>