Compare commits
No commits in common. "c0c2c59ea318805e9021d8bb8ceb5b7f91f93cb1" and "8a39714a25c230d2a9d927b07dc4b9611b210246" have entirely different histories.
c0c2c59ea3
...
8a39714a25
@ -20,9 +20,5 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult OtSTatus()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PSTW_CentralSystem.Areas.OTcalculate.Models
|
||||
{
|
||||
[Table("otstatus")]
|
||||
public class OtStatusModel
|
||||
{
|
||||
[Key]
|
||||
public int StatusId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Month { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Year { get; set; }
|
||||
|
||||
public DateTime SubmitDate { get; set; }
|
||||
|
||||
public string HodStatus { get; set; } = "Pending";
|
||||
|
||||
// JSON array of ApprovalUpdateLog
|
||||
public string? HodUpdate { get; set; }
|
||||
|
||||
public string HrStatus { get; set; } = "Pending";
|
||||
|
||||
// JSON array of ApprovalUpdateLog
|
||||
public string? HrUpdate { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -78,35 +78,30 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
columns.RelativeColumn(2.7f); // Description
|
||||
});
|
||||
|
||||
// Header Row
|
||||
table.Header(header =>
|
||||
{
|
||||
// Row 1 — grouped headers
|
||||
header.Cell().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(5).Text("Date").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().ColumnSpan(3).Background("#dceefb").Border(0.25f).Padding(5).Text("Office Hours\n(8:30 - 17:30)").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().ColumnSpan(3).Background("#edf2f7").Border(0.25f).Padding(5).Text("After Office Hours\n(17:30 - 8:30)").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Net OT Hours").FontSize(9).Bold().AlignCenter();
|
||||
void AddHeaderCell(string text, string bgColor)
|
||||
{
|
||||
header.Cell().Background(bgColor).Border(0.25f).Padding(5).Text(text).FontSize(9).Bold().AlignCenter();
|
||||
}
|
||||
|
||||
AddHeaderCell("Date", "#d0ead2");
|
||||
AddHeaderCell("From\n(Office)", "#dceefb");
|
||||
AddHeaderCell("To\n(Office)", "#dceefb");
|
||||
AddHeaderCell("Break\n(Office)", "#dceefb");
|
||||
AddHeaderCell("From\n(After)", "#edf2f7");
|
||||
AddHeaderCell("To\n(After)", "#edf2f7");
|
||||
AddHeaderCell("Break\n(After)", "#edf2f7");
|
||||
AddHeaderCell("Total OT\nHours", "#fdebd0");
|
||||
AddHeaderCell("Break Hours\n(min)", "#fdebd0");
|
||||
AddHeaderCell("Net OT Hours", "#fdebd0");
|
||||
if (departmentId == 2)
|
||||
header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(5).Text("Station").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(5).Text("Days").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(5).Text("Description").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
// Row 2 — subheaders only for grouped columns
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("Break").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("Break").FontSize(9).Bold().AlignCenter();
|
||||
AddHeaderCell("Station", "#d0f0ef");
|
||||
AddHeaderCell("Days", "#e0f7da");
|
||||
AddHeaderCell("Description", "#e3f2fd");
|
||||
});
|
||||
|
||||
|
||||
// Data Rows
|
||||
double totalOTSum = 0;
|
||||
int totalBreakSum = 0;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
@{
|
||||
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Rate Update";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
<th class="header-orange" rowspan="2" v-if="isPSTWAIR">Station</th>
|
||||
<th class="header-green" rowspan="2">Days</th>
|
||||
<th class="header-blue" rowspan="2">Description</th>
|
||||
<th class="header-green" rowspan="2" v-if="!isAlreadySubmitted">Action</th>
|
||||
<th class="header-green" rowspan="2">Action</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="header-blue">From</th>
|
||||
@ -134,7 +134,7 @@
|
||||
{{ record.otDescription }}
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="!isAlreadySubmitted">
|
||||
<td>
|
||||
<button class="btn btn-light border rounded-circle me-1" title="Edit Record" v-on:click="editRecord(index)">
|
||||
<i class="bi bi-pencil-fill text-warning fs-5"></i>
|
||||
</button>
|
||||
@ -167,33 +167,10 @@
|
||||
<button class="btn btn-dark btn-sm" v-on:click="downloadPdf">
|
||||
<i class="bi bi-download"></i> Save
|
||||
</button>
|
||||
<button class="btn btn-success btn-sm"
|
||||
v-on:click="openSubmitModal"
|
||||
:disabled="isSubmitting || isAlreadySubmitted">
|
||||
<i class="bi bi-send"></i> {{ isAlreadySubmitted ? 'Submitted' : 'Submit' }}
|
||||
<button class="btn btn-success btn-sm" v-on:click="submitRecords">
|
||||
<i class="bi bi-send"></i> Submit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Submit Modal -->
|
||||
<div v-if="showSubmitModal" class="modal show d-block" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content p-3">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Submit OT Records</h5>
|
||||
<button type="button" class="btn-close" v-on:click="showSubmitModal = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="file" class="form-control" v-on:change="handleFileChange" accept=".pdf" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success" v-on:click="submitToHod">Submit</button>
|
||||
<button class="btn btn-secondary" v-on:click="showSubmitModal = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -212,10 +189,7 @@
|
||||
selectedYear: currentYear,
|
||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i),
|
||||
expandedDescriptions: {},
|
||||
showSubmitModal: false,
|
||||
submitFile: null,
|
||||
submittedStatus: {},
|
||||
expandedDescriptions: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -243,10 +217,6 @@
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
};
|
||||
},
|
||||
isAlreadySubmitted() {
|
||||
const key = `${this.selectedYear}-${String(this.selectedMonth).padStart(2, '0')}`;
|
||||
return !!this.submittedStatus[key];
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@ -255,21 +225,8 @@
|
||||
methods: {
|
||||
async initUserAndRecords() {
|
||||
await this.fetchUser();
|
||||
if (this.userId) {
|
||||
await this.fetchOtRecords();
|
||||
await this.fetchSubmissionStatus();
|
||||
}
|
||||
if (this.userId) await this.fetchOtRecords();
|
||||
},
|
||||
async fetchSubmissionStatus() {
|
||||
try {
|
||||
const res = await fetch(`/OvertimeAPI/GetSubmissionStatus/${this.userId}`);
|
||||
const data = await res.json();
|
||||
this.submittedStatus = data; // expect format like { '2025-04': true }
|
||||
} catch (err) {
|
||||
console.error("Submission status fetch error:", err);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchUser() {
|
||||
try {
|
||||
const res = await fetch('/IdentityAPI/GetUserInformation', { method: 'POST' });
|
||||
@ -385,57 +342,43 @@
|
||||
alert("An error occurred while generating the PDF.");
|
||||
}
|
||||
},
|
||||
openSubmitModal() {
|
||||
this.showSubmitModal = true;
|
||||
},
|
||||
handleFileChange(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file && file.type !== 'application/pdf') {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
this.submitFile = file;
|
||||
},
|
||||
async submitRecords() {
|
||||
try {
|
||||
const recordsToSubmit = this.filteredRecords.map(record => ({
|
||||
overtimeId: record.overtimeId, // Make sure to include the ID for updates
|
||||
otDate: record.otDate,
|
||||
officeFrom: record.officeFrom,
|
||||
officeTo: record.officeTo,
|
||||
officeBreak: record.officeBreak,
|
||||
afterFrom: record.afterFrom,
|
||||
afterTo: record.afterTo,
|
||||
afterBreak: record.afterBreak,
|
||||
stationId: record.stationId,
|
||||
otDescription: record.otDescription,
|
||||
otDays: record.otDays,
|
||||
filePath: record.filePath, // Include existing file path
|
||||
userId: this.userId
|
||||
// Add other relevant fields if necessary
|
||||
}));
|
||||
|
||||
async submitToHod() {
|
||||
this.isSubmitting = true;
|
||||
try {
|
||||
if (!this.submitFile) {
|
||||
alert("Please upload a PDF file.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.submitFile);
|
||||
|
||||
// Add month & year selection logic if needed
|
||||
formData.append("month", new Date().getMonth() + 1);
|
||||
formData.append("year", new Date().getFullYear());
|
||||
|
||||
try {
|
||||
const response = await fetch("/OvertimeAPI/SubmitOvertimeRecords", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Submission failed");
|
||||
alert("Submission successful!");
|
||||
this.showSubmitModal = false;
|
||||
|
||||
const key = `${this.selectedYear}-${String(this.selectedMonth).padStart(2, '0')}`;
|
||||
this.submittedStatus[key] = true;
|
||||
this.showSubmitModal = false;
|
||||
|
||||
} catch (err) {
|
||||
alert("Error: " + err.message);
|
||||
}
|
||||
} finally {
|
||||
this.isSubmitting = false;
|
||||
const res = await fetch('/OvertimeAPI/SubmitOvertimeRecords', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(recordsToSubmit)
|
||||
});
|
||||
if (res.ok) {
|
||||
alert("Overtime records submitted for review.");
|
||||
} else {
|
||||
alert("Submission failed: " + await res.text());
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Submission error:", err);
|
||||
alert("An error occurred during submission.");
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
app.mount("#app");
|
||||
</script>
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
@{
|
||||
ViewData["Title"] = "Overtime Status";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<style>
|
||||
.hodstatus, .hrstatus {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="otStatusApp" class="container mt-4">
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month</th>
|
||||
<th>Year</th>
|
||||
<th>Submitted On</th>
|
||||
<th>HOD Status</th>
|
||||
<th>HR Status</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="status in statusList" :key="status.statusId">
|
||||
<td>{{ getMonthName(status.month) }}</td>
|
||||
<td>{{ status.year }}</td>
|
||||
<td>{{ formatDate(status.submitDate) }}</td>
|
||||
<td :class="status.hodStatus.toLowerCase()">{{ status.hodStatus }}</td>
|
||||
<td :class="status.hrStatus.toLowerCase()">{{ status.hrStatus }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" v-on:click ="viewUpdates(status)">View Updates</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Modal -->
|
||||
<div v-if="selectedStatus" class="modal-mask">
|
||||
<div class="modal-container">
|
||||
<h5>Status History</h5>
|
||||
<p><strong>HOD Updates:</strong></p>
|
||||
<ul>
|
||||
<li v-for="update in parseJson(selectedStatus.hodUpdate)">
|
||||
{{ formatUpdate(update) }}
|
||||
</li>
|
||||
</ul>
|
||||
<p><strong>HR Updates:</strong></p>
|
||||
<ul>
|
||||
<li v-for="update in parseJson(selectedStatus.hrUpdate)">
|
||||
{{ formatUpdate(update) }}
|
||||
</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary" v-on:click ="selectedStatus = null">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
statusList: [],
|
||||
selectedStatus: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
fetch('/OvertimeAPI/GetUserOtStatus')
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
.then(data => this.statusList = data)
|
||||
.catch(err => console.error("Fetch error:", err));
|
||||
},
|
||||
methods: {
|
||||
formatDate(dateStr) {
|
||||
return new Date(dateStr).toLocaleDateString();
|
||||
},
|
||||
getMonthName(month) {
|
||||
return new Date(2000, month - 1, 1).toLocaleString('default', { month: 'long' });
|
||||
},
|
||||
parseJson(jsonStr) {
|
||||
try {
|
||||
return jsonStr ? JSON.parse(jsonStr) : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
formatUpdate(update) {
|
||||
return `${update.timestamp} - ${update.updatedBy} changed ${update.field} from '${update.oldValue}' to '${update.newValue}'`;
|
||||
},
|
||||
viewUpdates(status) {
|
||||
this.selectedStatus = status;
|
||||
}
|
||||
}
|
||||
}).mount('#otStatusApp');
|
||||
</script>
|
||||
}
|
||||
@ -554,87 +554,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
|
||||
}
|
||||
|
||||
[HttpPost("SubmitOvertimeRecords")]
|
||||
public async Task<IActionResult> SubmitOvertimeRecords([FromForm] IFormFile file, [FromForm] int month, [FromForm] int year)
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
if (file == null || file.Length == 0)
|
||||
return BadRequest("No file uploaded.");
|
||||
|
||||
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Media", "Overtime");
|
||||
|
||||
if (!Directory.Exists(uploadsFolder))
|
||||
Directory.CreateDirectory(uploadsFolder);
|
||||
|
||||
var fileName = $"OT_{userId}_{year}_{month}_{DateTime.Now.Ticks}.pdf";
|
||||
var filePath = Path.Combine(uploadsFolder, fileName);
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var statusRecord = new OtStatusModel
|
||||
{
|
||||
UserId = userId,
|
||||
Month = month,
|
||||
Year = year,
|
||||
SubmitDate = DateTime.Now,
|
||||
HodStatus = "Pending",
|
||||
HrStatus = "Pending",
|
||||
FilePath = $"/Media/Overtime/{fileName}"
|
||||
};
|
||||
|
||||
_centralDbContext.OtStatus.Add(statusRecord);
|
||||
await _centralDbContext.SaveChangesAsync();
|
||||
|
||||
return Ok(new { message = "Overtime records submitted successfully." });
|
||||
}
|
||||
|
||||
[HttpGet("CheckSubmissionStatus")]
|
||||
public IActionResult CheckSubmissionStatus(int month, int year)
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
var isSubmitted = _centralDbContext.OtStatus
|
||||
.Any(s => s.UserId == userId && s.Month == month && s.Year == year);
|
||||
|
||||
return Ok(new { isSubmitted });
|
||||
}
|
||||
|
||||
[HttpGet("GetSubmissionStatus/{userId}")]
|
||||
public IActionResult GetSubmissionStatus(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statuses = _centralDbContext.OtStatus
|
||||
.Where(s => s.UserId == userId)
|
||||
.OrderByDescending(s => s.SubmitDate)
|
||||
.Select(s => new
|
||||
{
|
||||
s.UserId,
|
||||
s.Month,
|
||||
s.Year,
|
||||
s.HodStatus,
|
||||
s.HrStatus,
|
||||
s.SubmitDate
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(statuses);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch submission statuses.");
|
||||
return StatusCode(500, "Error retrieving submission statuses.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ot Edit
|
||||
@ -696,23 +615,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OtStatus
|
||||
[HttpGet("GetUserOtStatus")]
|
||||
public IActionResult GetUserOtStatus()
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
var records = _centralDbContext.OtStatus
|
||||
.Where(s => s.UserId == userId)
|
||||
.OrderByDescending(s => s.Year).ThenByDescending(s => s.Month)
|
||||
.ToList();
|
||||
|
||||
return Ok(records);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -104,7 +104,5 @@ namespace PSTW_CentralSystem.DBContext
|
||||
public DbSet<StateModel> States { get; set; }
|
||||
public DbSet<WeekendModel> Weekends { get; set; }
|
||||
public DbSet<OtRegisterModel> Otregisters { get; set; }
|
||||
public DbSet<OtStatusModel> OtStatus { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,11 +539,6 @@
|
||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Records</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="Overtime" asp-action="OtStatus" aria-expanded="false">
|
||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Status</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user