Compare commits
2 Commits
9603402fee
...
b9bc8fbcee
| Author | SHA1 | Date | |
|---|---|---|---|
| b9bc8fbcee | |||
| f6ddf1d9c1 |
125
app/Filament/Resources/InternshipResource.php
Normal file
125
app/Filament/Resources/InternshipResource.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\InternshipResource\Pages;
|
||||||
|
use App\Filament\Resources\InternshipResource\RelationManagers;
|
||||||
|
use App\Models\Internship;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||||
|
use Filament\Forms\Components\Repeater;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
|
|
||||||
|
class InternshipResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Internship::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationGroup = 'Internships';
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-academic-cap';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
TextInput::make('title')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
|
||||||
|
TextInput::make('location')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
|
||||||
|
Textarea::make('title_description')
|
||||||
|
->label('Description (show on modal)')
|
||||||
|
->required()
|
||||||
|
->maxLength(65535)
|
||||||
|
->columnSpan('full'),
|
||||||
|
|
||||||
|
TextInput::make('salary_per_month')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->label('Salary per month')
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('salary_currency')
|
||||||
|
->options(getCurrencies())
|
||||||
|
->required()
|
||||||
|
->label('Salary currency')
|
||||||
|
->searchable()
|
||||||
|
->default('USD'),
|
||||||
|
|
||||||
|
Repeater::make('bullets')
|
||||||
|
->schema([
|
||||||
|
TextInput::make('bullet')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
])
|
||||||
|
->minItems(1)
|
||||||
|
->defaultItems(1)
|
||||||
|
->columnSpan('full'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('title')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('title_description')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('salary_per_month')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('salary_currency')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('location')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('salary')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
RelationManagers\ApplicationsRelationManager::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListInternships::route('/'),
|
||||||
|
'create' => Pages\CreateInternship::route('/create'),
|
||||||
|
'edit' => Pages\EditInternship::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\InternshipResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\InternshipResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateInternship extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InternshipResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\InternshipResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\InternshipResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditInternship extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InternshipResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\InternshipResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\InternshipResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListInternships extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = InternshipResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\InternshipResource\RelationManagers;
|
||||||
|
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||||
|
use App\Models\InternshipApplication; // Added this use statement
|
||||||
|
|
||||||
|
class ApplicationsRelationManager extends RelationManager
|
||||||
|
{
|
||||||
|
protected static string $relationship = 'applications';
|
||||||
|
|
||||||
|
// Add this line to define the inverse relationship
|
||||||
|
protected static ?string $model = InternshipApplication::class;
|
||||||
|
|
||||||
|
public function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\DatePicker::make('birthdate')
|
||||||
|
->required(),
|
||||||
|
Forms\Components\FileUpload::make('resume_file')
|
||||||
|
->required()
|
||||||
|
->acceptedFileTypes(['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])
|
||||||
|
->disk('public') // or your preferred disk
|
||||||
|
->directory('resumes'),
|
||||||
|
Forms\Components\TextInput::make('email')
|
||||||
|
->email()
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('phone_number')
|
||||||
|
->required()
|
||||||
|
->maxLength(20),
|
||||||
|
Forms\Components\Textarea::make('cover_letter')
|
||||||
|
->maxLength(65535)
|
||||||
|
->nullable()
|
||||||
|
->columnSpan('full'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->recordTitleAttribute('name')
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('email')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('phone_number')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->headerActions([
|
||||||
|
Tables\Actions\CreateAction::make(),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ use App\Filament\Resources\NewsResource;
|
|||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListNews extends ListRecords
|
class ManageNews extends ListRecords
|
||||||
{
|
{
|
||||||
protected static string $resource = NewsResource::class;
|
protected static string $resource = NewsResource::class;
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,44 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Models\Internship;
|
use App\Models\Internship;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\InternshipApplication; // Changed from App\Models\Application
|
||||||
|
|
||||||
class InternshipsPageController extends Controller
|
class InternshipsPageController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('web.pages.internships.index');
|
$internships = Internship::query()->get();
|
||||||
|
|
||||||
|
return view('web.pages.internships.index', compact('internships'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
dd($request->all());
|
$validatedData = $request->validate([
|
||||||
|
'internship_id' => 'required|exists:internships,id',
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'birthdate' => 'required|date',
|
||||||
|
'resume_file' => 'required|file|mimes:pdf,doc,docx|max:2048',
|
||||||
|
'email' => 'required|email|max:255',
|
||||||
|
'phone_number' => 'required|string',
|
||||||
|
'cover_letter' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resumePath = $request->file('resume_file')->store('resumes');
|
||||||
|
|
||||||
|
InternshipApplication::create([ // Changed to InternshipApplication::create
|
||||||
|
'internship_id' => $validatedData['internship_id'],
|
||||||
|
'name' => $validatedData['name'],
|
||||||
|
'birthdate' => $validatedData['birthdate'],
|
||||||
|
'resume_file' => $resumePath,
|
||||||
|
'email' => $validatedData['email'],
|
||||||
|
'phone_number' => $validatedData['phone_number'],
|
||||||
|
'cover_letter' => $validatedData['cover_letter'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Your application has been submitted successfully!',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Internship $internship)
|
public function show(Internship $internship)
|
||||||
|
|||||||
@@ -3,8 +3,26 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use App\Models\InternshipApplication;
|
||||||
|
|
||||||
class Internship extends Model
|
class Internship extends Model
|
||||||
{
|
{
|
||||||
//
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'title_description',
|
||||||
|
'salary_per_month',
|
||||||
|
'bullets',
|
||||||
|
'location',
|
||||||
|
'salary_currency',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'bullets' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function applications(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(InternshipApplication::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/Models/InternshipApplication.php
Normal file
24
app/Models/InternshipApplication.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class InternshipApplication extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'internship_id',
|
||||||
|
'name',
|
||||||
|
'birthdate',
|
||||||
|
'resume_file',
|
||||||
|
'email',
|
||||||
|
'phone_number',
|
||||||
|
'cover_letter',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function internship(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Internship::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,33 +3,158 @@
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
| Livewire Temporary File Uploads
|
| Class Namespace
|
||||||
|--------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| By default, Livewire will upload temporary files to the local disk
|
| This value sets the root class namespace for Livewire component classes in
|
||||||
| and clear them after a maximum of 5 minutes. You may customize
|
| your application. This value will change where component auto-discovery
|
||||||
| this process by changing the "disk" and "max_upload_time" properties.
|
| finds components. It's also referenced by the file creation commands.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'class_namespace' => 'App\\Livewire',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| View Path
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value is used to specify where Livewire component Blade templates are
|
||||||
|
| stored when running file creation commands like `artisan make:livewire`.
|
||||||
|
| It is also used if you choose to omit a component's render() method.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'view_path' => resource_path('views/livewire'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Layout
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| The view that will be used as the layout when rendering a single component
|
||||||
|
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
|
||||||
|
| In this case, the view returned by CreatePost will render into $slot.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'layout' => 'components.layouts.app',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Lazy Loading Placeholder
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Livewire allows you to lazy load components that would otherwise slow down
|
||||||
|
| the initial page load. Every component can have a custom placeholder or
|
||||||
|
| you can define the default placeholder view for all components below.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'lazy_placeholder' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Temporary File Uploads
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||||
|
| before the file is stored permanently. All file uploads are directed to
|
||||||
|
| a global endpoint for temporary storage. You may configure this below:
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'temporary_file_upload' => [
|
'temporary_file_upload' => [
|
||||||
'disk' => 'public', // This is the default disk for temporary uploads.
|
'disk' => null, // Example: 'local', 's3' | Default: 'default'
|
||||||
'rules' => null, // Set this to null to use default upload rules (e.g., file size, mime types).
|
'rules' => ['required', 'file', 'max:100000'], // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||||
'middleware' => null, // Set this to null to use default middleware for temporary uploads.
|
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
||||||
'max_upload_time' => 30, // The maximum number of minutes a temporary file can be stored.
|
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||||
|
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||||
|
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||||
|
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||||
|
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||||
|
],
|
||||||
|
'max_upload_time' => 20, // Max duration (in minutes) before an upload is invalidated...
|
||||||
|
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
| Livewire App URL
|
| Render On Redirect
|
||||||
|--------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This is the app URL that Livewire will use to make requests to.
|
| This value determines if Livewire will run a component's `render()` method
|
||||||
| If you're using a custom domain, you may need to change this.
|
| after a redirect has been triggered using something like `redirect(...)`
|
||||||
|
| Setting this to true will render the view once more before redirecting
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'app_url' => env('APP_URL'),
|
'render_on_redirect' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Eloquent Model Binding
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Previous versions of Livewire supported binding directly to eloquent model
|
||||||
|
| properties using wire:model by default. However, this behavior has been
|
||||||
|
| deemed too "magical" and has therefore been put under a feature flag.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'legacy_model_binding' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Auto-inject Frontend Assets
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default, Livewire automatically injects its JavaScript and CSS into the
|
||||||
|
| <head> and <body> of pages containing Livewire components. By disabling
|
||||||
|
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'inject_assets' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Navigate (SPA mode)
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
|
||||||
|
| will prevent the default link handling and instead request those pages
|
||||||
|
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'navigate' => [
|
||||||
|
'show_progress_bar' => true,
|
||||||
|
'progress_bar_color' => '#2299dd',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| HTML Morph Markers
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
|
||||||
|
| after each update. To make this process more reliable, Livewire injects
|
||||||
|
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'inject_morph_markers' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
| Pagination Theme
|
||||||
|
|---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When enabling Livewire's pagination feature by using the `WithPagination`
|
||||||
|
| trait, Livewire will use Tailwind templates to render pagination views
|
||||||
|
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'pagination_theme' => 'tailwind',
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->foreignId('internship_id')->nullable()->constrained()->onDelete('set null');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropConstrainedForeignId('internship_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('internships', function (Blueprint $table) {
|
||||||
|
$table->string('title');
|
||||||
|
$table->text('title_description');
|
||||||
|
$table->string('salary_per_month');
|
||||||
|
$table->json('bullets');
|
||||||
|
$table->string('location');
|
||||||
|
$table->string('salary_currency');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('internships', function (Blueprint $table) {
|
||||||
|
$table->dropColumn([
|
||||||
|
'title',
|
||||||
|
'title_description',
|
||||||
|
'salary_per_month',
|
||||||
|
'bullets',
|
||||||
|
'location',
|
||||||
|
'salary_currency',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('internship_applications', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('internship_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->string('name');
|
||||||
|
$table->date('birthdate');
|
||||||
|
$table->string('resume_file');
|
||||||
|
$table->string('email');
|
||||||
|
$table->string('phone_number');
|
||||||
|
$table->text('cover_letter')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('internship_applications');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['internship_id']);
|
||||||
|
$table->dropColumn('internship_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->foreignId('internship_id')->nullable()->constrained()->onDelete('set null');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
122
resources/views/web/components/application_modal.blade.php
Normal file
122
resources/views/web/components/application_modal.blade.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<div class="modal fade" id="applyInternshipModal" tabindex="-1" aria-labelledby="applyInternshipModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content rounded-3">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="applicationModalLabel">Apply for <span id="jobTitle"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h6 id="jobLocation"></h6>
|
||||||
|
<p id="jobSalary"></p>
|
||||||
|
<p id="jobDescription"></p>
|
||||||
|
<form id="applicationForm" enctype="multipart/form-data" action="{{ $jobType === 'internship' ? route('internship.store') : route('career.store') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@if($jobType === 'internship')
|
||||||
|
<input type="hidden" name="internship_id" id="jobIdInput" value="{{ $jobId }}">
|
||||||
|
@else
|
||||||
|
<input type="hidden" name="career_id" id="jobIdInput" value="{{ $jobId }}">
|
||||||
|
@endif
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="name" class="form-label">Name <span> *</span></label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="birthdate" class="form-label">Birthdate <span> *</span></label>
|
||||||
|
<input type="date" class="form-control" id="birthdate" name="birthdate" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="email" class="form-label">Email <span> *</span></label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="phone_number" class="form-label">Phone Number <span> *</span></label>
|
||||||
|
<input type="text" class="form-control" id="phone_number" name="phone_number" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="resume_file" class="form-label">Resume (PDF, DOC, DOCX) <span> *</span></label>
|
||||||
|
<input type="file" class="form-control" id="resume_file" name="resume_file" accept=".pdf,.doc,.docx" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cover_letter" class="form-label">Cover Letter (Optional)</label>
|
||||||
|
<textarea class="form-control" id="cover_letter" name="cover_letter" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit Application</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var applicationModal = document.getElementById('applyInternshipModal'); // Changed ID here
|
||||||
|
if (applicationModal) { // Add a check for modal existence
|
||||||
|
applicationModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
var button = event.relatedTarget; // Button that triggered the modal
|
||||||
|
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
|
||||||
|
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
|
||||||
|
|
||||||
|
var modalTitle = applicationModal.querySelector('#jobTitle');
|
||||||
|
var modalLocation = applicationModal.querySelector('#jobLocation');
|
||||||
|
var modalSalary = applicationModal.querySelector('#jobSalary');
|
||||||
|
var modalDescription = applicationModal.querySelector('#jobDescription');
|
||||||
|
var jobIdInput = applicationModal.querySelector('#jobIdInput');
|
||||||
|
var applicationForm = applicationModal.querySelector('#applicationForm');
|
||||||
|
|
||||||
|
// Set dynamic values
|
||||||
|
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
|
||||||
|
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
|
||||||
|
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
|
||||||
|
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
|
||||||
|
jobIdInput.value = jobId;
|
||||||
|
|
||||||
|
// Set form action dynamically
|
||||||
|
if (jobType === 'internship') {
|
||||||
|
applicationForm.action = '{{ route('internship.store') }}';
|
||||||
|
jobIdInput.name = 'internship_id';
|
||||||
|
} else if (jobType === 'career') {
|
||||||
|
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
|
||||||
|
jobIdInput.name = 'career_id';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission with AJAX
|
||||||
|
document.getElementById('applicationForm').addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let form = e.target;
|
||||||
|
let formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.message) {
|
||||||
|
alert(data.message);
|
||||||
|
var modal = bootstrap.Modal.getInstance(applicationModal);
|
||||||
|
modal.hide();
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred. Please try again.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="price__area-item-btn">
|
<div class="price__area-item-btn">
|
||||||
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applicationModal" data-career-title="{{ $career->title }}" data-career-location="{{ $career->location }}" data-career-salary="{{ $career->salary_per_month }}" data-career-description="{{ $career->title_description }}" data-career-id="{{ $career->id }}">Apply Now<i class="flaticon-right-up"></i></button>
|
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applyInternshipModal" data-job-id="{{ $career->id }}" data-job-type="career" data-career-title="{{ $career->title }}" data-career-location="{{ $career->location }}" data-career-salary="{{ $career->salary_per_month }}" data-career-description="{{ $career->title_description }}">Apply Now<i class="flaticon-right-up"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
@@ -59,114 +59,77 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Pricing Plan Area End -->
|
<!-- Pricing Plan Area End -->
|
||||||
|
|
||||||
<!-- Application Modal -->
|
|
||||||
<div class="modal fade" id="applicationModal" tabindex="-1" aria-labelledby="applicationModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content rounded-3">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="applicationModalLabel">Apply for <span id="jobTitle"></span></h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<h6 id="jobLocation"></h6>
|
|
||||||
<p id="jobSalary"></p>
|
|
||||||
<p id="jobDescription"></p>
|
|
||||||
<form id="applicationForm" enctype="multipart/form-data" action="{{ route('applications.store') }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="career_id" id="careerId">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="name" class="form-label">Name <span> *</span></label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="birthdate" class="form-label">Birthdate <span> *</span></label>
|
|
||||||
<input type="date" class="form-control" id="birthdate" name="birthdate" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="email" class="form-label">Email <span> *</span></label>
|
|
||||||
<input type="email" class="form-control" id="email" name="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="phone_number" class="form-label">Phone Number <span> *</span></label>
|
|
||||||
<input type="text" class="form-control" id="phone_number" name="phone_number" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="resume_file" class="form-label">Resume (PDF, DOC, DOCX) <span> *</span></label>
|
|
||||||
<input type="file" class="form-control" id="resume_file" name="resume_file" accept=".pdf,.doc,.docx" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="cover_letter" class="form-label">Cover Letter (Optional)</label>
|
|
||||||
<textarea class="form-control" id="cover_letter" name="cover_letter" rows="5"></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit Application</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
{{-- Application Modal --}}
|
||||||
|
@include('web.components.application_modal', ['jobId' => null, 'jobType' => 'career']) {{-- jobId will be set by JS from button data attributes --}}
|
||||||
|
|
||||||
@push('footer-js')
|
@push('footer-js')
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var applicationModal = document.getElementById('applicationModal');
|
var applicationModal = document.getElementById('applyInternshipModal');
|
||||||
applicationModal.addEventListener('show.bs.modal', function (event) {
|
if (applicationModal) {
|
||||||
var button = event.relatedTarget;
|
applicationModal.addEventListener('show.bs.modal', function (event) {
|
||||||
var careerTitle = button.getAttribute('data-career-title');
|
var button = event.relatedTarget; // Button that triggered the modal
|
||||||
var careerLocation = button.getAttribute('data-career-location');
|
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
|
||||||
var careerSalary = button.getAttribute('data-career-salary');
|
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
|
||||||
var careerDescription = button.getAttribute('data-career-description');
|
|
||||||
var careerId = button.getAttribute('data-career-id');
|
|
||||||
|
|
||||||
var modalTitle = applicationModal.querySelector('#jobTitle');
|
var modalTitle = applicationModal.querySelector('#jobTitle');
|
||||||
var modalLocation = applicationModal.querySelector('#jobLocation');
|
var modalLocation = applicationModal.querySelector('#jobLocation');
|
||||||
var modalSalary = applicationModal.querySelector('#jobSalary');
|
var modalSalary = applicationModal.querySelector('#jobSalary');
|
||||||
var modalDescription = applicationModal.querySelector('#jobDescription');
|
var modalDescription = applicationModal.querySelector('#jobDescription');
|
||||||
var modalCareerId = applicationModal.querySelector('#careerId');
|
var jobIdInput = applicationModal.querySelector('#jobIdInput');
|
||||||
|
var applicationForm = applicationModal.querySelector('#applicationForm');
|
||||||
|
|
||||||
modalTitle.textContent = careerTitle;
|
// Set dynamic values
|
||||||
modalLocation.textContent = 'Location: ' + careerLocation;
|
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
|
||||||
modalSalary.textContent = 'Salary: ' + careerSalary + ' / Per monthly';
|
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
|
||||||
modalDescription.textContent = careerDescription;
|
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
|
||||||
modalCareerId.value = careerId;
|
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
|
||||||
});
|
jobIdInput.value = jobId;
|
||||||
|
|
||||||
// Handle form submission with AJAX
|
// Set form action dynamically
|
||||||
document.getElementById('applicationForm').addEventListener('submit', function (e) {
|
if (jobType === 'internship') {
|
||||||
e.preventDefault();
|
applicationForm.action = '{{ route('internship.store') }}';
|
||||||
|
jobIdInput.name = 'internship_id';
|
||||||
let form = e.target;
|
} else if (jobType === 'career') {
|
||||||
let formData = new FormData(form);
|
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
|
||||||
|
jobIdInput.name = 'career_id';
|
||||||
fetch(form.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.message) {
|
|
||||||
alert(data.message);
|
|
||||||
var modal = bootstrap.Modal.getInstance(applicationModal);
|
|
||||||
modal.hide();
|
|
||||||
form.reset();
|
|
||||||
} else {
|
|
||||||
alert('Error: ' + data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('An error occurred. Please try again.');
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// Handle form submission with AJAX
|
||||||
|
document.getElementById('applicationForm').addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let form = e.target;
|
||||||
|
let formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.message) {
|
||||||
|
alert(data.message);
|
||||||
|
var modal = bootstrap.Modal.getInstance(applicationModal);
|
||||||
|
modal.hide();
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred. Please try again.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
@extends('web.layouts.app')
|
||||||
|
|
||||||
|
@section('title', $career->title)
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<!-- Main Content for Single Career Page -->
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ $career->title }}</h1>
|
||||||
|
<p><strong>{{ __('Location:') }}</strong> {{ $career->location }}</p>
|
||||||
|
<p><strong>{{ __('Salary:') }}</strong> {{ $career->salary_per_month }} {{ $career->salary_currency }}</p>
|
||||||
|
|
||||||
|
<h2>{{ __('Description') }}</h2>
|
||||||
|
<p>{{ $career->title_description }}</p>
|
||||||
|
|
||||||
|
@if ($career->bullets)
|
||||||
|
<h3>{{ __('Responsibilities and Qualifications') }}</h3>
|
||||||
|
<ul>
|
||||||
|
@foreach ($career->bullets as $bullet)
|
||||||
|
<li>{{ $bullet['bullet'] }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Apply Button - Triggers Modal -->
|
||||||
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#applyInternshipModal">
|
||||||
|
{{ __('Apply for this Career') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Application Modal -->
|
||||||
|
@include('web.components.application_modal', ['jobId' => $career->id, 'jobType' => 'career'])
|
||||||
|
@endsection
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
@extends('web.layouts.app')
|
||||||
|
|
||||||
|
@push('header-css')
|
||||||
|
<style>
|
||||||
|
#applicationForm > label > span {
|
||||||
|
color: var(--gujurly-primary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<!-- Breadcrumb Area Start -->
|
||||||
|
<div class="breadcrumb__area" style="background-image: url('/web/assets/img/page/breadcrumb.jpg');">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-12">
|
||||||
|
<div class="breadcrumb__area-content">
|
||||||
|
<h2>Internships</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a><i class="fa-regular fa-angle-right"></i></li>
|
||||||
|
<li>Internships</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Breadcrumb Area End -->
|
||||||
|
<!-- Pricing Plan Area Start -->
|
||||||
|
<div class="price__area section-padding">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-4 col-md-6 xl-mb-25 wow fadeInUp" data-wow-delay=".4s">
|
||||||
|
@forelse ($internships as $internship)
|
||||||
|
<div class="price__area-item">
|
||||||
|
<div class="price__area-item-price">
|
||||||
|
<span>{{ $internship->title }}</span>
|
||||||
|
<h3>{{ $internship->location }}</h3>
|
||||||
|
<h2>{{ $internship->salary_per_month }} {{ $internship->salary_currency }}</h2>
|
||||||
|
<span>Per monthly</span>
|
||||||
|
</div>
|
||||||
|
<div class="price__area-item-list">
|
||||||
|
<ul>
|
||||||
|
@foreach($internship->bullets as $bullet)
|
||||||
|
<li><i class="flaticon-checked"></i>{{ $bullet['bullet'] ?? '' }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="price__area-item-btn">
|
||||||
|
<button type="button" class="build_button apply-now-button" data-bs-toggle="modal" data-bs-target="#applyInternshipModal" data-job-id="{{ $internship->id }}" data-job-type="internship" data-internship-title="{{ $internship->title }}" data-internship-location="{{ $internship->location }}" data-internship-salary="{{ $internship->salary_per_month }}" data-internship-description="{{ $internship->title_description }}">Apply Now<i class="flaticon-right-up"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<span>No internships found...</span>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Pricing Plan Area End -->
|
||||||
|
|
||||||
|
{{-- Application Modal --}}
|
||||||
|
@include('web.components.application_modal', ['jobId' => null, 'jobType' => 'internship']) {{-- jobId will be set by JS from button data attributes --}}
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer-js')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var applicationModal = document.getElementById('applyInternshipModal');
|
||||||
|
if (applicationModal) {
|
||||||
|
applicationModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
var button = event.relatedTarget; // Button that triggered the modal
|
||||||
|
var jobId = button.getAttribute('data-job-id'); // Generic attribute for job ID
|
||||||
|
var jobType = button.getAttribute('data-job-type'); // Generic attribute for job type
|
||||||
|
|
||||||
|
var modalTitle = applicationModal.querySelector('#jobTitle');
|
||||||
|
var modalLocation = applicationModal.querySelector('#jobLocation');
|
||||||
|
var modalSalary = applicationModal.querySelector('#jobSalary');
|
||||||
|
var modalDescription = applicationModal.querySelector('#jobDescription');
|
||||||
|
var jobIdInput = applicationModal.querySelector('#jobIdInput');
|
||||||
|
var applicationForm = applicationModal.querySelector('#applicationForm');
|
||||||
|
|
||||||
|
// Set dynamic values
|
||||||
|
modalTitle.textContent = button.getAttribute('data-' + jobType + '-title');
|
||||||
|
modalLocation.textContent = 'Location: ' + button.getAttribute('data-' + jobType + '-location');
|
||||||
|
modalSalary.textContent = 'Salary: ' + button.getAttribute('data-' + jobType + '-salary') + ' / Per monthly';
|
||||||
|
modalDescription.textContent = button.getAttribute('data-' + jobType + '-description');
|
||||||
|
jobIdInput.value = jobId;
|
||||||
|
|
||||||
|
// Set form action dynamically
|
||||||
|
if (jobType === 'internship') {
|
||||||
|
applicationForm.action = '{{ route('internship.store') }}';
|
||||||
|
jobIdInput.name = 'internship_id';
|
||||||
|
} else if (jobType === 'career') {
|
||||||
|
applicationForm.action = '{{ route('applications.store') }}'; // Assuming careers still use applications.store
|
||||||
|
jobIdInput.name = 'career_id';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission with AJAX
|
||||||
|
document.getElementById('applicationForm').addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let form = e.target;
|
||||||
|
let formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.message) {
|
||||||
|
alert(data.message);
|
||||||
|
var modal = bootstrap.Modal.getInstance(applicationModal);
|
||||||
|
modal.hide();
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred. Please try again.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
@extends('web.layouts.app')
|
||||||
|
|
||||||
|
@section('title', $internship->title)
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<!-- Main Content for Single Internship Page -->
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ $internship->title }}</h1>
|
||||||
|
<p><strong>{{ __('Location:') }}</strong> {{ $internship->location }}</p>
|
||||||
|
<p><strong>{{ __('Salary:') }}</strong> {{ $internship->salary_per_month }} {{ $internship->salary_currency }}</p>
|
||||||
|
|
||||||
|
<h2>{{ __('Description') }}</h2>
|
||||||
|
<p>{{ $internship->title_description }}</p>
|
||||||
|
|
||||||
|
@if ($internship->bullets)
|
||||||
|
<h3>{{ __('Responsibilities and Qualifications') }}</h3>
|
||||||
|
<ul>
|
||||||
|
@foreach ($internship->bullets as $bullet)
|
||||||
|
<li>{{ $bullet['bullet'] }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Apply Button - Triggers Modal -->
|
||||||
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#applyInternshipModal">
|
||||||
|
{{ __('Apply for this Internship') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Application Modal -->
|
||||||
|
@include('web.components.application_modal', ['jobId' => $internship->id, 'jobType' => 'internship'])
|
||||||
|
@endsection
|
||||||
|
|||||||
Reference in New Issue
Block a user