fix: синхронизации лекций
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m3s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 25s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 7s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m3s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 25s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 7s
This commit is contained in:
@@ -19,8 +19,66 @@ public interface IModeusApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modeus API response models
|
// Modeus API response models
|
||||||
public record ModeusEvent(string Id, string Name, DateTime StartsAt, DateTime EndsAt, string? RoomId, string? TeacherId, string? TypeId);
|
public class ModeusEvent
|
||||||
public record ModeusEventsResponse(List<ModeusEvent> Events);
|
{
|
||||||
|
public string Id { get; init; } = string.Empty;
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
public string? NameShort { get; init; }
|
||||||
|
public string? Description { get; init; }
|
||||||
|
public string? TypeId { get; init; }
|
||||||
|
public DateTime StartsAt { get; init; }
|
||||||
|
public DateTime EndsAt { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("_links")]
|
||||||
|
public ModeusEventLinks? Links { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModeusEventLinks
|
||||||
|
{
|
||||||
|
[JsonPropertyName("course-unit-realization")]
|
||||||
|
public ModeusHrefLink? CourseUnitRealization { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModeusEventsResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("_embedded")]
|
||||||
|
public ModeusEventsEmbedded? Embedded { get; init; }
|
||||||
|
public List<ModeusEvent>? Events { get; init; }
|
||||||
|
public ModeusPage? Page { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IReadOnlyList<ModeusEvent> EventItems => Embedded?.Events ?? Events ?? [];
|
||||||
|
}
|
||||||
|
public class ModeusEventsEmbedded
|
||||||
|
{
|
||||||
|
public List<ModeusEvent>? Events { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("course-unit-realizations")]
|
||||||
|
public List<ModeusCourseUnitRealization>? CourseUnitRealizations { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("event-rooms")]
|
||||||
|
public List<ModeusEventRoom>? EventRooms { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("event-teams")]
|
||||||
|
public List<ModeusEventTeam>? EventTeams { get; init; }
|
||||||
|
|
||||||
|
public List<ModeusRoom>? Rooms { get; init; }
|
||||||
|
}
|
||||||
|
public record ModeusHrefLink(string? Href);
|
||||||
|
public record ModeusCourseUnitRealization(string Id, string Name, string? NameShort);
|
||||||
|
public class ModeusEventRoom
|
||||||
|
{
|
||||||
|
public string Id { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("_links")]
|
||||||
|
public ModeusEventRoomLinks? Links { get; init; }
|
||||||
|
}
|
||||||
|
public class ModeusEventRoomLinks
|
||||||
|
{
|
||||||
|
public ModeusHrefLink? Event { get; init; }
|
||||||
|
public ModeusHrefLink? Room { get; init; }
|
||||||
|
}
|
||||||
|
public record ModeusEventTeam(string EventId, int? Size);
|
||||||
public record ModeusBuilding(string? Id, string? Name, string? NameShort, string? Address);
|
public record ModeusBuilding(string? Id, string? Name, string? NameShort, string? Address);
|
||||||
public record ModeusRoom(string Id, string Name, string? NameShort, ModeusBuilding? Building, int? TotalCapacity, int? WorkingCapacity);
|
public record ModeusRoom(string Id, string Name, string? NameShort, ModeusBuilding? Building, int? TotalCapacity, int? WorkingCapacity);
|
||||||
public record ModeusRoomsEmbedded(List<ModeusRoom>? Rooms);
|
public record ModeusRoomsEmbedded(List<ModeusRoom>? Rooms);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using UniVerse.Application.DTOs.Sync;
|
using UniVerse.Application.DTOs.Sync;
|
||||||
@@ -21,11 +22,30 @@ public class ModeusApiClient : IModeusApiClient
|
|||||||
|
|
||||||
public async Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request)
|
public async Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request)
|
||||||
{
|
{
|
||||||
var body = new { specialtyCode = request.SpecialtyCode, timeMin = request.TimeMin, timeMax = request.TimeMax, typeId = request.TypeId };
|
const int pageSize = 900;
|
||||||
|
var specialtyCodes = string.IsNullOrWhiteSpace(request.SpecialtyCode)
|
||||||
|
? []
|
||||||
|
: request.SpecialtyCode
|
||||||
|
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var typeIds = request.TypeId ?? [];
|
||||||
|
var body = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["size"] = pageSize,
|
||||||
|
["timeMin"] = request.TimeMin,
|
||||||
|
["timeMax"] = request.TimeMax,
|
||||||
|
["specialtyCode"] = specialtyCodes
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeIds.Count > 0)
|
||||||
|
body["typeId"] = typeIds;
|
||||||
|
|
||||||
var response = await _http.PostAsJsonAsync("/api/proxy/events/search", body);
|
var response = await _http.PostAsJsonAsync("/api/proxy/events/search", body);
|
||||||
|
var requestJson = JsonSerializer.Serialize(body);
|
||||||
await EnsureSuccessAsync(response, "Modeus events search",
|
await EnsureSuccessAsync(response, "Modeus events search",
|
||||||
$"specialtyCode={request.SpecialtyCode ?? "<empty>"}, timeMin={request.TimeMin:O}, timeMax={request.TimeMax:O}, typeId=[{string.Join(", ", request.TypeId ?? [])}]");
|
BuildEventsRequestSummary(pageSize, specialtyCodes, request.TimeMin, request.TimeMax, typeIds, requestJson));
|
||||||
return await response.Content.ReadFromJsonAsync<ModeusEventsResponse>() ?? new(new());
|
return await ReadJsonAsync<ModeusEventsResponse>(response, "Modeus events search",
|
||||||
|
BuildEventsRequestSummary(pageSize, specialtyCodes, request.TimeMin, request.TimeMax, typeIds, requestJson))
|
||||||
|
?? new ModeusEventsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ModeusRoomsResponse> SearchRoomsAsync()
|
public async Task<ModeusRoomsResponse> SearchRoomsAsync()
|
||||||
@@ -50,7 +70,9 @@ public class ModeusApiClient : IModeusApiClient
|
|||||||
await EnsureSuccessAsync(response, "Modeus rooms search",
|
await EnsureSuccessAsync(response, "Modeus rooms search",
|
||||||
$"name=<empty>, sort=+building.name,+name, size={pageSize}, page={page}, deleted=false");
|
$"name=<empty>, sort=+building.name,+name, size={pageSize}, page={page}, deleted=false");
|
||||||
|
|
||||||
var payload = await response.Content.ReadFromJsonAsync<ModeusRoomsResponse>() ?? new ModeusRoomsResponse();
|
var payload = await ReadJsonAsync<ModeusRoomsResponse>(response, "Modeus rooms search",
|
||||||
|
$"name=<empty>, sort=+building.name,+name, size={pageSize}, page={page}, deleted=false")
|
||||||
|
?? new ModeusRoomsResponse();
|
||||||
allRooms.AddRange(payload.RoomItems);
|
allRooms.AddRange(payload.RoomItems);
|
||||||
|
|
||||||
totalPages = payload.Page?.TotalPages ?? page + 1;
|
totalPages = payload.Page?.TotalPages ?? page + 1;
|
||||||
@@ -66,15 +88,54 @@ public class ModeusApiClient : IModeusApiClient
|
|||||||
if (response.IsSuccessStatusCode) return;
|
if (response.IsSuccessStatusCode) return;
|
||||||
|
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
if (responseBody.Length > 2000)
|
|
||||||
responseBody = string.Concat(responseBody.AsSpan(0, 2000), "...<truncated>");
|
|
||||||
|
|
||||||
throw new HttpRequestException(
|
throw new HttpRequestException(
|
||||||
$"{operation} failed with HTTP {(int)response.StatusCode} {response.ReasonPhrase}. Request: {requestSummary}. Response body: {responseBody}",
|
$"{operation} failed with HTTP {(int)response.StatusCode} {response.ReasonPhrase}. Request: {requestSummary}. Response body: {Truncate(responseBody)}",
|
||||||
null,
|
null,
|
||||||
response.StatusCode);
|
response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildEventsRequestSummary(
|
||||||
|
int size,
|
||||||
|
IReadOnlyList<string> specialtyCodes,
|
||||||
|
DateTime? timeMin,
|
||||||
|
DateTime? timeMax,
|
||||||
|
IReadOnlyList<string> typeIds,
|
||||||
|
string requestJson)
|
||||||
|
{
|
||||||
|
var typeFilter = typeIds.Count > 0 ? $"typeId=[{string.Join(", ", typeIds)}]" : "typeId=<omitted>";
|
||||||
|
return $"size={size}, specialtyCode=[{string.Join(", ", specialtyCodes)}], timeMin={timeMin:O}, timeMax={timeMax:O}, {typeFilter}. Request JSON: {requestJson}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<T?> ReadJsonAsync<T>(HttpResponseMessage response, string operation, string requestSummary)
|
||||||
|
{
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
var contentType = response.Content.Headers.ContentType?.ToString() ?? "<empty>";
|
||||||
|
var contentLength = response.Content.Headers.ContentLength?.ToString() ?? "<unknown>";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(responseBody))
|
||||||
|
{
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"{operation} returned HTTP {(int)response.StatusCode} {response.ReasonPhrase} with an empty response body. Request: {requestSummary}. Content-Type: {contentType}. Content-Length: {contentLength}.",
|
||||||
|
null,
|
||||||
|
response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<T>(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{operation} returned invalid JSON. Request: {requestSummary}. Content-Type: {contentType}. Response body: {Truncate(responseBody)}",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Truncate(string value) =>
|
||||||
|
value.Length > 2000 ? string.Concat(value.AsSpan(0, 2000), "...<truncated>") : value;
|
||||||
|
|
||||||
public async Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname)
|
public async Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname)
|
||||||
{
|
{
|
||||||
var response = await _http.GetFromJsonAsync<List<ModeusEmployee>>(
|
var response = await _http.GetFromJsonAsync<List<ModeusEmployee>>(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Text.Json;
|
||||||
using UniVerse.Application.DTOs.Sync;
|
using UniVerse.Application.DTOs.Sync;
|
||||||
using UniVerse.Application.Interfaces;
|
using UniVerse.Application.Interfaces;
|
||||||
using UniVerse.Domain.Entities;
|
using UniVerse.Domain.Entities;
|
||||||
@@ -26,15 +27,58 @@ public class ScheduleSyncService : IScheduleSyncService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var events = await _modeus.SearchEventsAsync(request);
|
var events = await _modeus.SearchEventsAsync(request);
|
||||||
foreach (var ev in events.Events)
|
foreach (var ev in events.EventItems)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ev.Id) || string.IsNullOrWhiteSpace(ev.Name))
|
||||||
|
{
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var courseUnitId = GetHrefId(ev.Links?.CourseUnitRealization?.Href);
|
||||||
|
var courseUnit = events.Embedded?.CourseUnitRealizations?
|
||||||
|
.FirstOrDefault(c => c.Id == courseUnitId);
|
||||||
|
var courseExternalId = courseUnit?.Id ?? ev.TypeId ?? ev.Id;
|
||||||
|
var courseName = courseUnit?.Name ?? ev.Name;
|
||||||
|
var location = await UpsertEventLocationAsync(events, ev.Id);
|
||||||
|
var maxEnrollments = events.Embedded?.EventTeams?
|
||||||
|
.FirstOrDefault(team => team.EventId == ev.Id)?.Size ?? 0;
|
||||||
|
var startsAt = EnsureUtc(ev.StartsAt);
|
||||||
|
var endsAt = EnsureUtc(ev.EndsAt);
|
||||||
|
|
||||||
var existing = await _db.Lectures.FirstOrDefaultAsync(l => l.ExternalId == ev.Id);
|
var existing = await _db.Lectures.FirstOrDefaultAsync(l => l.ExternalId == ev.Id);
|
||||||
if (existing != null) { updated++; existing.StartsAt = ev.StartsAt; existing.EndsAt = ev.EndsAt; existing.UpdatedAt = DateTime.UtcNow; }
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Title = ev.Name;
|
||||||
|
existing.Description = ev.Description;
|
||||||
|
existing.StartsAt = startsAt;
|
||||||
|
existing.EndsAt = endsAt;
|
||||||
|
existing.LocationId = location?.Id;
|
||||||
|
existing.MaxEnrollments = maxEnrollments;
|
||||||
|
existing.UpdatedAt = DateTime.UtcNow;
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var course = await _db.Courses.FirstOrDefaultAsync(c => c.ExternalId == ev.TypeId);
|
var course = await _db.Courses.FirstOrDefaultAsync(c => c.ExternalId == courseExternalId);
|
||||||
if (course == null) { course = new Course { Name = ev.Name, ExternalId = ev.TypeId, IsSynced = true }; _db.Courses.Add(course); await _db.SaveChangesAsync(); }
|
if (course == null)
|
||||||
_db.Lectures.Add(new Lecture { CourseId = course.Id, Title = ev.Name, ExternalId = ev.Id, StartsAt = ev.StartsAt, EndsAt = ev.EndsAt });
|
{
|
||||||
|
course = new Course { Name = courseName, ExternalId = courseExternalId, IsSynced = true };
|
||||||
|
_db.Courses.Add(course);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.Lectures.Add(new Lecture
|
||||||
|
{
|
||||||
|
CourseId = course.Id,
|
||||||
|
LocationId = location?.Id,
|
||||||
|
Title = ev.Name,
|
||||||
|
Description = ev.Description,
|
||||||
|
ExternalId = ev.Id,
|
||||||
|
StartsAt = startsAt,
|
||||||
|
EndsAt = endsAt,
|
||||||
|
MaxEnrollments = maxEnrollments
|
||||||
|
});
|
||||||
created++;
|
created++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +90,9 @@ public class ScheduleSyncService : IScheduleSyncService
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Schedule sync failed");
|
_logger.LogError(ex, "Schedule sync failed");
|
||||||
|
var specialtyCodes = string.IsNullOrWhiteSpace(request.SpecialtyCode)
|
||||||
|
? []
|
||||||
|
: request.SpecialtyCode.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
var result = new SyncResultDto(created, updated, skipped, ex.Message, BuildErrorDetails(
|
var result = new SyncResultDto(created, updated, skipped, ex.Message, BuildErrorDetails(
|
||||||
ex,
|
ex,
|
||||||
stage,
|
stage,
|
||||||
@@ -53,10 +100,14 @@ public class ScheduleSyncService : IScheduleSyncService
|
|||||||
updated,
|
updated,
|
||||||
skipped,
|
skipped,
|
||||||
[
|
[
|
||||||
$"specialtyCode={request.SpecialtyCode ?? "<empty>"}",
|
"size=900",
|
||||||
|
$"requestJson={BuildScheduleRequestJson(request)}",
|
||||||
|
$"specialtyCode=[{string.Join(", ", specialtyCodes)}]",
|
||||||
$"timeMin={request.TimeMin:O}",
|
$"timeMin={request.TimeMin:O}",
|
||||||
$"timeMax={request.TimeMax:O}",
|
$"timeMax={request.TimeMax:O}",
|
||||||
$"typeId=[{string.Join(", ", request.TypeId ?? [])}]"
|
request.TypeId is { Count: > 0 }
|
||||||
|
? $"typeId=[{string.Join(", ", request.TypeId)}]"
|
||||||
|
: "typeId=<omitted>"
|
||||||
]));
|
]));
|
||||||
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "failed", result);
|
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "failed", result);
|
||||||
return result;
|
return result;
|
||||||
@@ -129,6 +180,48 @@ public class ScheduleSyncService : IScheduleSyncService
|
|||||||
|
|
||||||
public Task<SyncStatusDto> GetLastSyncStatusAsync() => Task.FromResult(_lastStatus);
|
public Task<SyncStatusDto> GetLastSyncStatusAsync() => Task.FromResult(_lastStatus);
|
||||||
|
|
||||||
|
private async Task<Location?> UpsertEventLocationAsync(ModeusEventsResponse events, string eventId)
|
||||||
|
{
|
||||||
|
var roomId = events.Embedded?.EventRooms?
|
||||||
|
.Select(eventRoom => new
|
||||||
|
{
|
||||||
|
EventId = GetHrefId(eventRoom.Links?.Event?.Href),
|
||||||
|
RoomId = GetHrefId(eventRoom.Links?.Room?.Href)
|
||||||
|
})
|
||||||
|
.FirstOrDefault(link => link.EventId == eventId)
|
||||||
|
?.RoomId;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(roomId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var room = events.Embedded?.Rooms?.FirstOrDefault(item => item.Id == roomId);
|
||||||
|
if (room == null || string.IsNullOrWhiteSpace(room.Name))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var existing = await _db.Locations.FirstOrDefaultAsync(location => location.ExternalId == room.Id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Name = room.Name;
|
||||||
|
existing.Room = room.NameShort;
|
||||||
|
existing.Building = room.Building?.Name ?? room.Building?.NameShort;
|
||||||
|
existing.Address = room.Building?.Address;
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = new Location
|
||||||
|
{
|
||||||
|
Name = room.Name,
|
||||||
|
Room = room.NameShort,
|
||||||
|
Building = room.Building?.Name ?? room.Building?.NameShort,
|
||||||
|
Address = room.Building?.Address,
|
||||||
|
ExternalId = room.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.Locations.Add(location);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<string> BuildErrorDetails(
|
private static IReadOnlyList<string> BuildErrorDetails(
|
||||||
Exception exception,
|
Exception exception,
|
||||||
string stage,
|
string stage,
|
||||||
@@ -154,4 +247,43 @@ public class ScheduleSyncService : IScheduleSyncService
|
|||||||
details.AddRange(context);
|
details.AddRange(context);
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildScheduleRequestJson(SyncScheduleRequest request)
|
||||||
|
{
|
||||||
|
var specialtyCodes = string.IsNullOrWhiteSpace(request.SpecialtyCode)
|
||||||
|
? []
|
||||||
|
: request.SpecialtyCode.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var body = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["size"] = 900,
|
||||||
|
["timeMin"] = request.TimeMin,
|
||||||
|
["timeMax"] = request.TimeMax,
|
||||||
|
["specialtyCode"] = specialtyCodes
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.TypeId is { Count: > 0 })
|
||||||
|
body["typeId"] = request.TypeId;
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetHrefId(string? href)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(href))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var index = href.LastIndexOf('/');
|
||||||
|
return index >= 0 && index < href.Length - 1
|
||||||
|
? href[(index + 1)..]
|
||||||
|
: href;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime EnsureUtc(DateTime value) =>
|
||||||
|
value.Kind switch
|
||||||
|
{
|
||||||
|
DateTimeKind.Utc => value,
|
||||||
|
DateTimeKind.Local => value.ToUniversalTime(),
|
||||||
|
_ => DateTime.SpecifyKind(value, DateTimeKind.Local).ToUniversalTime()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,14 +51,16 @@ function toInputDateTime(date: Date) {
|
|||||||
return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16)
|
return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date()
|
const todayStart = new Date()
|
||||||
const inTwoWeeks = new Date(now)
|
todayStart.setHours(0, 0, 0, 0)
|
||||||
|
const inTwoWeeks = new Date(todayStart)
|
||||||
inTwoWeeks.setDate(inTwoWeeks.getDate() + 14)
|
inTwoWeeks.setDate(inTwoWeeks.getDate() + 14)
|
||||||
|
inTwoWeeks.setHours(23, 59, 0, 0)
|
||||||
|
|
||||||
const syncForm = ref({
|
const syncForm = ref({
|
||||||
specialtyCode: '',
|
specialtyCode: '',
|
||||||
typeIds: [] as ApiScheduleTypeId[],
|
typeIds: [] as ApiScheduleTypeId[],
|
||||||
timeMin: toInputDateTime(now),
|
timeMin: toInputDateTime(todayStart),
|
||||||
timeMax: toInputDateTime(inTwoWeeks),
|
timeMax: toInputDateTime(inTwoWeeks),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user