diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index dc5e71d..06635a4 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -13,15 +13,46 @@ public function index(Request $request){ return Inertia::render('Settings/Index', [ 'actions' => \App\Models\Action::query() - ->with('decisions') + ->with(['decisions', 'segment']) ->get(), 'decisions' => \App\Models\Decision::query() ->with('actions') + ->get(), + 'segments' => \App\Models\Segment::query() ->get() ] ); } + public function storeAction(Request $request) + { + $attributes = $request->validate([ + 'name' => 'required|string|max:50', + 'color_tag' => 'nullable|string|max:25', + 'segment_id' => 'nullable|integer|exists:segments,id', + 'decisions' => 'nullable|array', + 'decisions.*.id' => 'required_with:decisions.*|integer|exists:decisions,id', + 'decisions.*.name' => 'required_with:decisions.*|string|max:50', + ]); + + $decisionIds = collect($attributes['decisions'] ?? [])->pluck('id')->toArray(); + + \DB::transaction(function () use ($attributes, $decisionIds) { + /** @var \App\Models\Action $row */ + $row = \App\Models\Action::create([ + 'name' => $attributes['name'], + 'color_tag' => $attributes['color_tag'] ?? null, + 'segment_id' => $attributes['segment_id'] ?? null, + ]); + + if (!empty($decisionIds)) { + $row->decisions()->sync($decisionIds); + } + }); + + return to_route('settings')->with('success', 'Action created successfully!'); + } + public function updateAction(int $id, Request $request) { $row = \App\Models\Action::findOrFail($id); @@ -29,6 +60,7 @@ public function updateAction(int $id, Request $request) { $attributes = $request->validate([ 'name' => 'required|string|max:50', 'color_tag' => 'nullable|string|max:25', + 'segment_id' => 'nullable|integer|exists:segments,id', 'decisions' => 'nullable|array', 'decisions.*.id' => 'required_with:decisions.*|integer|exists:decisions,id', 'decisions.*.name' => 'required_with:decisions.*|string|max:50' @@ -39,7 +71,8 @@ public function updateAction(int $id, Request $request) { \DB::transaction(function() use ($attributes, $decisionIds, $row) { $row->update([ 'name' => $attributes['name'], - 'color_tag' => $attributes['color_tag'] + 'color_tag' => $attributes['color_tag'], + 'segment_id' => $attributes['segment_id'] ?? null, ]); $row->decisions()->sync($decisionIds); @@ -48,4 +81,56 @@ public function updateAction(int $id, Request $request) { return to_route('settings')->with('success', 'Update successful!'); } + + public function storeDecision(Request $request) + { + $attributes = $request->validate([ + 'name' => 'required|string|max:50', + 'color_tag' => 'nullable|string|max:25', + 'actions' => 'nullable|array', + 'actions.*.id' => 'required_with:actions.*|integer|exists:actions,id', + 'actions.*.name' => 'required_with:actions.*|string|max:50', + ]); + + $actionIds = collect($attributes['actions'] ?? [])->pluck('id')->toArray(); + + \DB::transaction(function () use ($attributes, $actionIds) { + /** @var \App\Models\Decision $row */ + $row = \App\Models\Decision::create([ + 'name' => $attributes['name'], + 'color_tag' => $attributes['color_tag'] ?? null, + ]); + + if (!empty($actionIds)) { + $row->actions()->sync($actionIds); + } + }); + + return to_route('settings')->with('success', 'Decision created successfully!'); + } + + public function updateDecision(int $id, Request $request) + { + $row = \App\Models\Decision::findOrFail($id); + + $attributes = $request->validate([ + 'name' => 'required|string|max:50', + 'color_tag' => 'nullable|string|max:25', + 'actions' => 'nullable|array', + 'actions.*.id' => 'required_with:actions.*|integer|exists:actions,id', + 'actions.*.name' => 'required_with:actions.*|string|max:50', + ]); + + $actionIds = collect($attributes['actions'] ?? [])->pluck('id')->toArray(); + + \DB::transaction(function () use ($attributes, $actionIds, $row) { + $row->update([ + 'name' => $attributes['name'], + 'color_tag' => $attributes['color_tag'] ?? null, + ]); + $row->actions()->sync($actionIds); + }); + + return to_route('settings')->with('success', 'Decision updated successfully!'); + } } diff --git a/app/Models/Action.php b/app/Models/Action.php index 2875c40..c3ef762 100644 --- a/app/Models/Action.php +++ b/app/Models/Action.php @@ -14,7 +14,7 @@ class Action extends Model use HasFactory; use Searchable; - protected $fillable = ['name', 'color_tag']; + protected $fillable = ['name', 'color_tag', 'segment_id']; public function decisions(): BelongsToMany { diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 0ea1e5a..e20e2c1 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -18,6 +18,7 @@ class Activity extends Model 'amount', 'note', 'action_id', + 'user_id', 'decision_id' ]; @@ -25,9 +26,20 @@ class Activity extends Model 'action_id', 'decision_id', 'client_case_id', + 'user_id', 'contract_id' ]; + protected static function booted(){ + static::creating(function (Activity $activity) { + if(!isset($activity->user_id)){ + $activity->user_id = auth()->id(); + } + }); + } + + + public function action(): BelongsTo { return $this->belongsTo(\App\Models\Action::class); diff --git a/app/Models/Decision.php b/app/Models/Decision.php index 6cb7d59..32d3329 100644 --- a/app/Models/Decision.php +++ b/app/Models/Decision.php @@ -13,6 +13,8 @@ class Decision extends Model /** @use HasFactory<\Database\Factories\DecisionFactory> */ use HasFactory; + protected $fillable = ['name', 'color_tag']; + public function actions(): BelongsToMany { return $this->belongsToMany(\App\Models\Action::class); diff --git a/database/migrations/2025_09_25_200801_alter_table_activities_add_user_id.php b/database/migrations/2025_09_25_200801_alter_table_activities_add_user_id.php new file mode 100644 index 0000000..b00c963 --- /dev/null +++ b/database/migrations/2025_09_25_200801_alter_table_activities_add_user_id.php @@ -0,0 +1,32 @@ +foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('activities', function (Blueprint $table) { + // Drop FK then column + if (Schema::hasColumn('activities', 'user_id')) { + $table->dropForeign(['user_id']); + $table->dropColumn('user_id'); + } + }); + } +}; diff --git a/database/migrations/2025_09_25_201020_table_documents.php b/database/migrations/2025_09_25_201020_table_documents.php new file mode 100644 index 0000000..1d27e55 --- /dev/null +++ b/database/migrations/2025_09_25_201020_table_documents.php @@ -0,0 +1,57 @@ +id(); + // Public identifier to reference documents externally + $table->uuid('uuid')->unique(); + + // Optional human-friendly title and description + $table->string('name'); + $table->text('description')->nullable(); + + // Uploader (nullable, to preserve docs if user is removed) + $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + + // Polymorphic relation to attach a document to any model (e.g., ClientCase, Contract, etc.) + $table->nullableMorphs('documentable'); // creates documentable_type + documentable_id (indexed) + + // Storage metadata + $table->string('disk', 50)->default('public'); + $table->string('path', 2048); // relative path within the disk + $table->string('file_name'); // stored file name (basename) + $table->string('original_name'); // original client file name + $table->string('extension', 20)->nullable(); + $table->string('mime_type', 100)->nullable(); + $table->unsignedBigInteger('size')->nullable(); // bytes + $table->string('checksum', 64)->nullable(); // e.g., SHA-256 + + // Visibility / flags + $table->boolean('is_public')->default(false); + + // Index to speed up path lookups per disk + $table->index(['disk', 'path']); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('documents'); + } +}; diff --git a/package-lock.json b/package-lock.json index 32d25b1..d87729b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "teren-app", + "name": "Teren-app", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/js/Pages/Settings/Index.vue b/resources/js/Pages/Settings/Index.vue index c9dfe91..6b27468 100644 --- a/resources/js/Pages/Settings/Index.vue +++ b/resources/js/Pages/Settings/Index.vue @@ -7,7 +7,8 @@ import DecisionTable from './Partials/DecisionTable.vue'; const props = defineProps({ actions: Array, - decisions: Array + decisions: Array, + segments: Array }); const activeTab = ref('actions') @@ -21,10 +22,10 @@ const activeTab = ref('actions')