Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3664 Created the CRUD operation for the project stages. #3665

Merged
merged 25 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
52b8aad
Created migration for the project stages
AnirudhAP2k Jun 17, 2024
e3f4a98
Created CRUD operation of the project stages
AnirudhAP2k Jun 19, 2024
2032451
CI Fixes
AnirudhAP2k Jun 19, 2024
55775f6
CI Fixes
AnirudhAP2k Jun 19, 2024
74f0b08
CI Fixes
AnirudhAP2k Jun 20, 2024
592fe99
Migratin CI fixes
AnirudhAP2k Jun 20, 2024
2c3a1e7
Migration fixes
AnirudhAP2k Jun 20, 2024
1a2d463
Migration fixes
AnirudhAP2k Jun 20, 2024
adb3456
Ci fixes
AnirudhAP2k Jun 20, 2024
e39f55e
CI fixes
AnirudhAP2k Jun 20, 2024
fdfef9c
CI fixes
AnirudhAP2k Jun 20, 2024
527ec4f
Minor fixes in migration
AnirudhAP2k Jun 20, 2024
7a669a1
Minor fixes in migration
AnirudhAP2k Jun 20, 2024
ebdda52
Added start date + duration column in the table + action items in fro…
AnirudhAP2k Jun 22, 2024
9341c54
Merge branch 'master' of https://github.com/coloredcow/portal into fe…
AnirudhAP2k Jun 22, 2024
10afc65
Minor changes
AnirudhAP2k Jun 22, 2024
c751f3d
Minor fixes
AnirudhAP2k Jun 22, 2024
c6d6f0a
Created a table to store the new list of the project stages
AnirudhAP2k Jun 24, 2024
a12bdfe
Merge branch 'master' of https://github.com/coloredcow/portal into fe…
AnirudhAP2k Jun 24, 2024
a6f3200
CI fixes
AnirudhAP2k Jun 24, 2024
d8cd211
Merge branch 'master' of https://github.com/coloredcow/portal into fe…
AnirudhAP2k Jun 27, 2024
6464667
Created expected end date column + displayed delay for overdie status…
AnirudhAP2k Jul 2, 2024
65a75e5
Merge branch 'master' of https://github.com/coloredcow/portal into fe…
AnirudhAP2k Jul 2, 2024
3459c85
CI fixes
AnirudhAP2k Jul 2, 2024
3f886b6
CI fixes
AnirudhAP2k Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Modules/Project/Entities/ProjectStages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Modules\Project\Entities;

use Illuminate\Database\Eloquent\Model;

class ProjectStages extends Model
{
protected $table = 'project_old_stages';
protected $guarded = [];
protected $fillables = ['project_id', 'stage_name', 'status', 'created_at', 'updated_at', 'end_date', 'comments'];

public function project()
{
return $this->belongsTo(Project::class, 'project_id', 'id');
}
}
14 changes: 14 additions & 0 deletions Modules/Project/Entities/ProjectStagesListing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Modules\Project\Entities;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class ProjectStagesListing extends Model
{
use HasFactory;
protected $table = 'project_stages_listing';
protected $guarded = [];
protected $fillables = ['name', 'created_at', 'updated_at'];
}
47 changes: 46 additions & 1 deletion Modules/Project/Http/Controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@
use Modules\Project\Entities\ProjectContract;
use Modules\Project\Http\Requests\ProjectRequest;
use Modules\Project\Rules\ProjectNameExist;
use Modules\Project\Services\ProjectService;

class ProjectController extends Controller
{
use AuthorizesRequests;

protected $service;
protected $projectService;

public function __construct(ProjectServiceContract $service)
public function __construct(ProjectServiceContract $service, ProjectService $projectService)
{
$this->authorizeResource(Project::class);
$this->service = $service;
$this->projectService = $projectService;
}

/**
Expand Down Expand Up @@ -109,6 +112,7 @@ public function show(Request $request, Project $project)
'effortData' => $effortData,
'totalEffort' => json_encode($totalEffort),
'dailyEffort' => $dailyEffort,
'stages' => $this->projectService->getProjectStages($project),
]);
}

Expand Down Expand Up @@ -197,6 +201,47 @@ public function projectResource()
'jobName' => $jobName,
]);
}

public function manageStage(Request $request)
{
$validatedData = $request->validate([
'project_id' => 'required|integer|exists:projects,id',
'newStages' => 'nullable|array',
'newStages.*.stage_name' => 'required|string',
'newStages.*.comments' => 'nullable|string',
'newStages.*.status' => 'nullable|string',
'newStages.*.expected_end_date' => 'required|date',
'deletedStages' => 'nullable|array',
'deletedStages.*' => 'required|integer|exists:project_old_stages,id',
'updatedStages' => 'nullable|array',
'updatedStages.*.id' => 'required|integer|exists:project_old_stages,id',
]);

if (empty($validatedData['deletedStages']) && empty($validatedData['newStages']) && empty($validatedData['updatedStages'])) {
return response()->json([
'status' => 400,
'error' => 'No New Changes Detected',
], 400);
}

if (! empty($validatedData['deletedStages'])) {
$this->projectService->removeStage($validatedData['deletedStages']);
}

if (! empty($validatedData['newStages'])) {
$this->projectService->storeStage($validatedData['newStages'], $validatedData['project_id']);
}

if (! empty($validatedData['updatedStages'])) {
$this->projectService->updateStage($validatedData['updatedStages']);
}

return response()->json([
'status' => 200,
'success' => 'Stages managed successfully!',
]);
}

// storing Contract related data here
private function getContractData(Project $project)
{
Expand Down
1 change: 1 addition & 0 deletions Modules/Project/Resources/views/layouts/master.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
@endsection

@section('css_scripts')
<link href="{{ mix('/css/project.css') }}" rel="stylesheet">
@endsection
196 changes: 194 additions & 2 deletions Modules/Project/Resources/views/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
</div>
<br>
@can('finance_reports.view')
<div class="card-header d-flex mb-4" data-toggle="collapse" data-target="#contract-history" >
<div class="card-header d-flex" data-toggle="collapse" data-target="#contract-history" >
<h4>Contract History</h4>
<span class ="arrow ml-auto">&#9660;</span>
</div>
Expand All @@ -320,7 +320,199 @@
</table>
</div>
<br>
<br>
@endcan
</div>
<div class="container" id="stages_app">
@include('project::subviews.project-stages')
</div>
<br>
<br>
@endsection
@section('vue_scripts')
<script>
new Vue({
el: '#stages_app',
data() {
return {
stages: [],
deletedStages: [],
projectId: "{{ $project->id }}",
loaderVisible: false,
submitButton: true,
editButtonStates: {},
dropdownVisibility: {},
currentDate: "{{ now()->toDateString() }}"
};
},
methods: {
convertToDateTime(seconds) {
const units = [
{ label: 'd', value: 24 * 3600 },
{ label: 'h', value: 3600 },
{ label: 'm', value: 60 },
{ label: 's', value: 1 }
];
return units.map(unit => {
const count = Math.floor(seconds / unit.value);
seconds %= unit.value;
return count ? `${count}${unit.label}` : '';
}).filter(Boolean).join(' ');
},
calculateDuration(stage) {
const duration = this.convertToDateTime(stage.duration);
const delay = this.calculateDelay(stage.end_date, stage.expected_end_date);
return delay ? `${duration} (<strong>Delay: </strong>${delay})` : duration;
},
calculateDelay(endDate, expectedEndDate) {
const delaySeconds = Math.floor((new Date(endDate) - new Date(expectedEndDate)) / 1000);
return delaySeconds > 0 ? this.convertToDateTime(delaySeconds) : null;
},
formattedDate(dateTime) {
return dateTime ? new Date(dateTime).toISOString().split('T')[0] : '';
},
formatDisplayDate(date) {
return new Date(date).toDateString().split(' ').slice(1).join(' ');
},
addStage() {
const newIndex = this.stages.length;
this.editStage(newIndex);
this.submitButton = false;
this.stages.push({
stage_name: '',
comments: '',
start_date: '',
expected_end_date: '',
end_date: '',
status: 'pending'
});
this.$nextTick(() => this.initializeRichTextEditor(newIndex));
},
editStage(index) {
this.submitButton = false;
this.$set(this.editButtonStates, index, true);
this.initializeRichTextEditor(index);
},
isEditing(index) {
return this.editButtonStates[index] || false;
},
deleteStage(stage) {
this.submitButton = false;
if (!this.deletedStages.includes(stage.id) && stage.id) {
this.deletedStages.push(stage.id);
}
var index = this.stages.indexOf(stage);
if (index !== -1) {
this.stages.splice(index, 1);
}
this.$nextTick(() => {
tinymce.remove();
this.stages.forEach((stage, index) => {
if (this.isEditing(index)) {
this.initializeRichTextEditor(index);
}
});
});
},
markStageAsUpdated(stage) {
if (stage.id) stage.isUpdated = true;
},
updateStatus(status, index) {
const stage = this.stages[index];
stage.status = status;
if (status === 'started') {
stage.start_date = new Date().toISOString();
stage.end_date = null;
} else if (status === 'completed') {
stage.end_date = new Date().toISOString();
} else if (status === 'pending') {
stage.start_date = null;
stage.end_date = null;
}
this.markStageAsUpdated(stage);
},
submitForm() {
const newStages = this.stages.filter(stage => !stage.id).map(this.cleanStageData);
const updatedStages = this.stages.filter(stage => stage.id && stage.isUpdated).map(this.cleanStageData);

this.toggleLoader();
axios.post('{{ route('projects.manage-stage') }}', {
newStages,
updatedStages,
deletedStages: this.deletedStages,
project_id: this.projectId,
_token: '{{ csrf_token() }}'
}).then(() => {
this.toggleLoader();
this.$toast.success('Stages Managed Successfully!');
location.reload(true);
}).catch(error => {
this.toggleLoader();
const errorMessage = error.response?.data?.message || error.response?.data?.error || "An error occurred. Please check console";
this.$toast.error(errorMessage);
console.error(error.response.data);
});
},
toggleLoader() {
this.loaderVisible = !this.loaderVisible;
},
cleanStageData(stage) {
const { comments, duration, start_date, expected_end_date, end_date, id, project_id, stage_name, status } = stage;
return { comments, duration, start_date, expected_end_date, end_date, id, project_id, stage_name, status };
},
dropdownClass(status) {
const statusClasses = {
pending: 'btn btn-theme-gray',
started: 'btn btn-theme-fog',
completed: 'btn btn-success',
overdue: 'btn btn-danger'
};
return `dropdown-content ${statusClasses[status] || 'btn btn-theme-gray'}`;
},
checkOverdueStatus(stage) {
if (new Date(stage.expected_end_date) < new Date(this.currentDate) && stage.status !== 'completed') {
stage.status = 'overdue';
}
},
initializeRichTextEditor(index) {
if (!tinymce.get(`stage-comments-${index}`)) {
tinymce.init({
selector: `#stage-comments-${index}`,
skin: "lightgray",
toolbar: "undo redo | formatselect | fontselect fontsizeselect bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
plugins: ["advlist lists autolink link image"],
font_formats: "Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;AkrutiKndPadmini=Akpdmi-n",
images_upload_url: "postAcceptor.php",
content_style: "body { font-size: 14pt; }",
automatic_uploads: false,
fontsize_formats: "8pt 10pt 12pt 14pt 16pt 18pt 24pt 36pt 48pt",
menubar: false,
statusbar: false,
entity_encoding: "raw",
forced_root_block: "",
force_br_newlines: true,
force_p_newlines: false,
width: 350,
convert_urls: false,
setup: editor => {
editor.on('input change keyup paste', () => {
this.stages[index].comments = editor.getContent();
this.markStageAsUpdated(this.stages[index]);
});
}
});
}
}
},
mounted() {
this.stages = @json($stages) || [];
this.stages.forEach((stage, index) => {
stage.started = ['started', 'completed'].includes(stage.status);
stage.completed = stage.status === 'completed';
stage.initialStatus = stage.status;
this.checkOverdueStatus(stage);
});
}
});
</script>

@endsection
Loading
Loading