wip - upload

This commit is contained in:
Dr Masroor Ehsan 2025-01-06 13:48:24 +06:00
parent e0b9f4e37e
commit 8d74373ad0
8 changed files with 408 additions and 9 deletions

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Controllers\HashidControllerBase;
use App\Models\Enums\Permission;
use App\Models\Study;
use Illuminate\Http\Request;
class AttachmentController extends HashidControllerBase
{
public function upload(Request $request)
{
abort_unless(auth()->user()->may(Permission::AttachmentUpload), 403);
$this->decodeKeys();
$study = Study::findOrFail($this->key);
$request->validate([
'file.*' => 'required|mimes:pdf,jpg,png|max:2048',
]);
foreach ($request->file('file') as $file) {
$study->addMedia($file)->toMediaCollection('uploads');
}
return response()->json(['success' => 'Files uploaded successfully']);
}
public function delete($mediaId)
{
abort_unless(auth()->user()->may(Permission::AttachmentUpload), 403);
$this->decodeKeys();
$study = Study::findOrFail($this->key);
$media = $study->getMedia('uploads')->where('id', (int) $mediaId)->first();
if ($media) {
$media->delete();
return redirect()->back()->with('success', 'File deleted successfully');
}
return redirect()->back()->with('error', 'File not found');
}
}

View File

@ -5,6 +5,7 @@
use App\Http\Controllers\HashidControllerBase;
use App\Http\Requests\StudyHistoryRequest;
use App\Models\Enums\Permission;
use App\Models\Study;
use App\Models\StudyDetails;
class StudyHistoryController extends HashidControllerBase
@ -14,8 +15,9 @@ public function view()
abort_unless(auth()->user()->may(Permission::StudyHistoryView), 403);
$this->decodeKeys();
$details = StudyDetails::historyOnly($this->key);
$study = Study::findOrFail($this->key);
return view('staff.history.view', compact('details'));
return view('staff.history.view', compact('details', 'study'));
}
public function edit()
@ -23,8 +25,9 @@ public function edit()
abort_unless(auth()->user()->may(Permission::StudyHistoryEdit), 403);
$this->decodeKeys();
$details = StudyDetails::historyOnly($this->key);
$study = Study::findOrFail($this->key);
return view('staff.history.edit', compact('details'));
return view('staff.history.edit', compact('details', 'study'));
}
public function save(StudyHistoryRequest $request)

View File

@ -13,10 +13,13 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Study extends BaseModel
class Study extends BaseModel implements HasMedia
{
use HashableId;
use InteractsWithMedia;
public function details(): HasOne
{

View File

@ -23,6 +23,7 @@
"rap2hpoutre/fast-excel": "^5.5",
"rawilk/laravel-settings": "^3.4",
"sentry/sentry-laravel": "^4.10",
"spatie/laravel-medialibrary": "^11.11",
"spatie/laravel-permission": "^6.10",
"vinkla/hashids": "^12.0",
"yajra/laravel-datatables": "^11.0"

280
config/media-library.php Normal file
View File

@ -0,0 +1,280 @@
<?php
return [
/*
* The disk on which to store added files and derived images by default. Choose
* one or more of the disks you've configured in config/filesystems.php.
*/
'disk_name' => env('MEDIA_DISK', 'public'),
/*
* The maximum file size of an item in bytes.
* Adding a larger file will result in an exception.
*/
'max_file_size' => 1024 * 1024 * 10, // 10MB
/*
* This queue connection will be used to generate derived and responsive images.
* Leave empty to use the default queue connection.
*/
'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'),
/*
* This queue will be used to generate derived and responsive images.
* Leave empty to use the default queue.
*/
'queue_name' => env('MEDIA_QUEUE', ''),
/*
* By default all conversions will be performed on a queue.
*/
'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true),
/*
* Should database transactions be run after database commits?
*/
'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true),
/*
* The fully qualified class name of the media model.
*/
'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
/*
* When enabled, media collections will be serialised using the default
* laravel model serialization behaviour.
*
* Keep this option disabled if using Media Library Pro components (https://medialibrary.pro)
*/
'use_default_collection_serialization' => false,
/*
* The fully qualified class name of the model used for temporary uploads.
*
* This model is only used in Media Library Pro (https://medialibrary.pro)
*/
'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class,
/*
* When enabled, Media Library Pro will only process temporary uploads that were uploaded
* in the same session. You can opt to disable this for stateless usage of
* the pro components.
*/
'enable_temporary_uploads_session_affinity' => true,
/*
* When enabled, Media Library pro will generate thumbnails for uploaded file.
*/
'generate_thumbnails_for_temporary_uploads' => true,
/*
* This is the class that is responsible for naming generated files.
*/
'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
/*
* The class that contains the strategy for determining a media file's path.
*/
'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
/*
* The class that contains the strategy for determining how to remove files.
*/
'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class,
/*
* Here you can specify which path generator should be used for the given class.
*/
'custom_path_generators' => [
// Model::class => PathGenerator::class
// or
// 'model_morph_alias' => PathGenerator::class
],
/*
* When urls to files get generated, this class will be called. Use the default
* if your files are stored locally above the site root or on s3.
*/
'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
/*
* Moves media on updating to keep path consistent. Enable it only with a custom
* PathGenerator that uses, for example, the media UUID.
*/
'moves_media_on_update' => false,
/*
* Whether to activate versioning when urls to files get generated.
* When activated, this attaches a ?v=xx query string to the URL.
*/
'version_urls' => false,
/*
* The media library will try to optimize all converted images by removing
* metadata and applying a little bit of compression. These are
* the optimizers that will be used by default.
*/
'image_optimizers' => [
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
'-m85', // set maximum quality to 85%
'--force', // ensure that progressive generation is always done also if a little bigger
'--strip-all', // this strips out all text information such as comments and EXIF data
'--all-progressive', // this will make sure the resulting image is a progressive one
],
Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
'--force', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Optipng::class => [
'-i0', // this will result in a non-interlaced, progressive scanned image
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
'-quiet', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Svgo::class => [
'--disable=cleanupIDs', // disabling because it is known to cause troubles
],
Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
'-b', // required parameter for this package
'-O3', // this produces the slowest but best results
],
Spatie\ImageOptimizer\Optimizers\Cwebp::class => [
'-m 6', // for the slowest compression method in order to get the best compression.
'-pass 10', // for maximizing the amount of analysis pass.
'-mt', // multithreading for some speed improvements.
'-q 90', // quality factor that brings the least noticeable changes.
],
Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
'-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).
'-j all', // number of jobs (worker threads, "all" uses all available cores).
'--min 0', // min quantizer for color (0-63).
'--max 63', // max quantizer for color (0-63).
'--minalpha 0', // min quantizer for alpha (0-63).
'--maxalpha 63', // max quantizer for alpha (0-63).
'-a end-usage=q', // rate control mode set to Constant Quality mode.
'-a tune=ssim', // SSIM as tune the encoder for distortion metric.
],
],
/*
* These generators will be used to create an image of media files.
*/
'image_generators' => [
Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
],
/*
* The path where to store temporary files while performing image conversions.
* If set to null, storage_path('media-library/temp') will be used.
*/
'temporary_directory_path' => null,
/*
* The engine that should perform the image conversions.
* Should be either `gd` or `imagick`.
*/
'image_driver' => env('IMAGE_DRIVER', 'gd'),
/*
* FFMPEG & FFProbe binaries paths, only used if you try to generate video
* thumbnails and have installed the php-ffmpeg/php-ffmpeg composer
* dependency.
*/
'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
/*
* Here you can override the class names of the jobs used by this package. Make sure
* your custom jobs extend the ones provided by the package.
*/
'jobs' => [
'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
],
/*
* When using the addMediaFromUrl method you may want to replace the default downloader.
* This is particularly useful when the url of the image is behind a firewall and
* need to add additional flags, possibly using curl.
*/
'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
/*
* When using the addMediaFromUrl method the SSL is verified by default.
* This is option disables SSL verification when downloading remote media.
* Please note that this is a security risk and should only be false in a local environment.
*/
'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true),
'remote' => [
/*
* Any extra headers that should be included when uploading media to
* a remote disk. Even though supported headers may vary between
* different drivers, a sensible default has been provided.
*
* Supported by S3: CacheControl, Expires, StorageClass,
* ServerSideEncryption, Metadata, ACL, ContentEncoding
*/
'extra_headers' => [
'CacheControl' => 'max-age=604800',
],
],
'responsive_images' => [
/*
* This class is responsible for calculating the target widths of the responsive
* images. By default we optimize for filesize and create variations that each are 30%
* smaller than the previous one. More info in the documentation.
*
* https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images
*/
'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
/*
* By default rendering media to a responsive image will add some javascript and a tiny placeholder.
* This ensures that the browser can already determine the correct layout.
* When disabled, no tiny placeholder is generated.
*/
'use_tiny_placeholders' => true,
/*
* This class will generate the tiny placeholder used for progressive image loading. By default
* the media library will use a tiny blurred jpg image.
*/
'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
],
/*
* When enabling this option, a route will be registered that will enable
* the Media Library Pro Vue and React components to move uploaded files
* in a S3 bucket to their right place.
*/
'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false),
/*
* When converting Media instances to response the media library will add
* a `loading` attribute to the `img` tag. Here you can set the default
* value of that attribute.
*
* Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction.
*
* More info: https://css-tricks.com/native-lazy-loading/
*/
'default_loading_attribute_value' => null,
/*
* You can specify a prefix for that is used for storing all media.
* If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory.
*/
'prefix' => env('MEDIA_PREFIX', '/attachments'),
/*
* When forcing lazy loading, media will be loaded even if you don't eager load media and you have
* disabled lazy loading globally in the service provider.
*/
'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true),
];

View File

@ -33,6 +33,8 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/Trumbowyg/2.27.3/plugins/cleanpaste/trumbowyg.cleanpaste.min.js"
integrity="sha512-UInqT8f+K1tkck6llPo0HDxlT/Zxv8t4OGeCuVfsIlXLrnP1ZKDGb+tBsBPMqDW15OcmV8NDfQe9+EaAG4aXeg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>
@endsection
@section('page-script')
@ -49,6 +51,20 @@
autogrow: true,
resetCss: true
});
Dropzone.options.fileDropzone = {
paramName: 'file',
maxFilesize: 2, // MB
acceptedFiles: '.pdf,.jpg,.png',
uploadMultiple: true,
parallelUploads: 3,
headers: {
'X-CSRF-TOKEN': "{{ csrf_token() }}"
},
success: function (file, response) {
console.log(response.success);
}
};
</script>
@endsection
@ -93,6 +109,35 @@
<button type="submit">Save</button>
</form>
<hr>
<h1>Upload Files</h1>
<!-- List of already uploaded files -->
<div class="uploaded-files">
<h2>Uploaded Files</h2>
<ul>
@foreach ($study->getMedia('uploads') as $media)
<li>
<i class="fa {{ $media->mime_type == 'application/pdf' ? 'fa-file-pdf' : 'fa-file-image' }}"></i>
<a href="{{ $media->getUrl() }}" target="_blank">{{ $media->file_name }}</a>
({{ $media->human_readable_size }}) - Uploaded on {{ $media->created_at->format('Y-m-d H:i') }}
<form action="{{ route('staff.attachment.delete', [$study->hash, $media->id]) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</li>
@endforeach
</ul>
</div>
<!-- Dropzone area -->
<form action="{{ route('staff.attachment.upload', $study->hash) }}" class="dropzone" id="file-dropzone">
@csrf
</form>
@endsection

View File

@ -31,28 +31,46 @@
<h4>Clinical Information</h4>
<h5>Clinical History</h5>
<div class="p-4 border-gray-100">
<div class="show-text" disabled name="clinical_history" id="clinical_history" >{!! $details->clinical_history !!}</di>
<h5>Clinical History</h5>
<div class="p-4 border-gray-100">
<div class="show-text" disabled name="clinical_history"
id="clinical_history">{!! $details->clinical_history !!}</di>
</div>
<x-section-border/>
<h5>surgical history</h5>
<div class="p-4 border-gray-100">
<div class="show-text" disabled name="surgical_history" id="surgical_history" >{!! $details->surgical_history !!}</div>
<div class="show-text" disabled name="surgical_history"
id="surgical_history">{!! $details->surgical_history !!}</div>
</div>
<x-section-border/>
<h5>lab results</h5>
<div class="p-4 border-gray-100">
<div class="show-text" disabled name="lab_results" id="lab_results" >{!! $details->lab_results !!}</div>
<div class="show-text" disabled name="lab_results" id="lab_results">{!! $details->lab_results !!}</div>
</div>
<x-section-border/>
<h5>clinical diagnosis</h5>
<div class="p-4 border-gray-100">
<div class="show-text" disabled name="clinical_diagnosis" id="clinical_diagnosis" >{!! $details->clinical_diagnosis !!}</div>
<div class="show-text" disabled name="clinical_diagnosis"
id="clinical_diagnosis">{!! $details->clinical_diagnosis !!}</div>
</div>
<!-- List of already uploaded files -->
<div class="uploaded-files">
<h2>Uploaded Files</h2>
<ul>
@foreach ($study->getMedia('uploads') as $media)
<li>
<i class="fa {{ $media->mime_type == 'application/pdf' ? 'fa-file-pdf' : 'fa-file-image' }}"></i>
<a href="{{ $media->getUrl() }}" target="_blank">{{ $media->file_name }}</a>
({{ $media->human_readable_size }}) - Uploaded
on {{ $media->created_at->format('Y-m-d H:i:s') }}
</li>
@endforeach
</ul>
</div>
@endsection

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\Guest\ViewSharedStudyController;
use App\Http\Controllers\Radiologist\ReportWriteController;
use App\Http\Controllers\SocialLoginController;
use App\Http\Controllers\Staff\AttachmentController;
use App\Http\Controllers\Staff\StudiesController;
use App\Http\Controllers\Staff\StudyHistoryController;
use App\Http\Controllers\Staff\StudyViewerController;
@ -61,6 +62,11 @@
Route::post('{hashid}', [StudyHistoryController::class, 'save'])->name('save');
});
Route::group(['prefix' => 'attachment', 'as' => 'attachment.'], function () {
Route::post('upload/{hashid}', [AttachmentController::class, 'upload'])->name('upload');
Route::delete('delete/{hashid}/{media}', [AttachmentController::class, 'delete'])->name('delete');
});
Route::group(['prefix' => 'meta', 'as' => 'meta.'], function () {
Route::get('{hashid}', [StudyMetadataController::class, 'view'])->name('view');
Route::get('{hashid}/edit', [StudyMetadataController::class, 'edit'])->name('edit');