Enum ve static kodlar
This commit is contained in:
parent
c6d2fbf30a
commit
a86d0f9fd1
6 changed files with 1900 additions and 2058 deletions
3073
ui/src/utils/erp.tsx
3073
ui/src/utils/erp.tsx
File diff suppressed because it is too large
Load diff
|
|
@ -27,6 +27,12 @@ import CollectionDialog from './CollectionDialog'
|
||||||
import EndorsementDialog from './EndorsementDialog'
|
import EndorsementDialog from './EndorsementDialog'
|
||||||
import Widget from '../../../components/common/Widget'
|
import Widget from '../../../components/common/Widget'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
|
import {
|
||||||
|
getCheckStatusColor,
|
||||||
|
getCheckStatusText,
|
||||||
|
getNoteStatusColor,
|
||||||
|
getNoteStatusText,
|
||||||
|
} from '@/utils/erp'
|
||||||
|
|
||||||
interface CheckNoteManagementProps {
|
interface CheckNoteManagementProps {
|
||||||
checks: FiCheck[]
|
checks: FiCheck[]
|
||||||
|
|
@ -236,52 +242,6 @@ const CheckNoteManagement: React.FC<CheckNoteManagementProps> = ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCheckStatusLabel = (status: CheckStatusEnum) => {
|
|
||||||
const statusLabels = {
|
|
||||||
[CheckStatusEnum.InHand]: 'Elde',
|
|
||||||
[CheckStatusEnum.Deposited]: 'Bankaya Verildi',
|
|
||||||
[CheckStatusEnum.Collected]: 'Tahsil Edildi',
|
|
||||||
[CheckStatusEnum.Bounced]: 'Karşılıksız',
|
|
||||||
[CheckStatusEnum.Endorsed]: 'Ciro Edildi',
|
|
||||||
[CheckStatusEnum.Cancelled]: 'İptal',
|
|
||||||
}
|
|
||||||
return statusLabels[status]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCheckStatusColor = (status: CheckStatusEnum) => {
|
|
||||||
const statusColors = {
|
|
||||||
[CheckStatusEnum.InHand]: 'bg-blue-100 text-blue-800',
|
|
||||||
[CheckStatusEnum.Deposited]: 'bg-yellow-100 text-yellow-800',
|
|
||||||
[CheckStatusEnum.Collected]: 'bg-green-100 text-green-800',
|
|
||||||
[CheckStatusEnum.Bounced]: 'bg-red-100 text-red-800',
|
|
||||||
[CheckStatusEnum.Endorsed]: 'bg-purple-100 text-purple-800',
|
|
||||||
[CheckStatusEnum.Cancelled]: 'bg-gray-100 text-gray-800',
|
|
||||||
}
|
|
||||||
return statusColors[status]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNoteStatusLabel = (status: NoteStatusEnum) => {
|
|
||||||
const statusLabels = {
|
|
||||||
[NoteStatusEnum.InHand]: 'Elde',
|
|
||||||
[NoteStatusEnum.Collected]: 'Tahsil Edildi',
|
|
||||||
[NoteStatusEnum.Overdue]: 'Vadesi Geçmiş',
|
|
||||||
[NoteStatusEnum.Endorsed]: 'Ciro Edildi',
|
|
||||||
[NoteStatusEnum.Cancelled]: 'İptal',
|
|
||||||
}
|
|
||||||
return statusLabels[status]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNoteStatusColor = (status: NoteStatusEnum) => {
|
|
||||||
const statusColors = {
|
|
||||||
[NoteStatusEnum.InHand]: 'bg-blue-100 text-blue-800',
|
|
||||||
[NoteStatusEnum.Collected]: 'bg-green-100 text-green-800',
|
|
||||||
[NoteStatusEnum.Overdue]: 'bg-red-100 text-red-800',
|
|
||||||
[NoteStatusEnum.Endorsed]: 'bg-purple-100 text-purple-800',
|
|
||||||
[NoteStatusEnum.Cancelled]: 'bg-gray-100 text-gray-800',
|
|
||||||
}
|
|
||||||
return statusColors[status]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTypeLabel = (type: CheckTypeEnum | NoteTypeEnum) => {
|
const getTypeLabel = (type: CheckTypeEnum | NoteTypeEnum) => {
|
||||||
if (type === CheckTypeEnum.Received || type === NoteTypeEnum.Received) {
|
if (type === CheckTypeEnum.Received || type === NoteTypeEnum.Received) {
|
||||||
return 'Alınan'
|
return 'Alınan'
|
||||||
|
|
@ -412,7 +372,7 @@ const CheckNoteManagement: React.FC<CheckNoteManagementProps> = ({
|
||||||
check.status,
|
check.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getCheckStatusLabel(check.status)}
|
{getCheckStatusText(check.status)}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -570,7 +530,7 @@ const CheckNoteManagement: React.FC<CheckNoteManagementProps> = ({
|
||||||
note.status,
|
note.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getNoteStatusLabel(note.status)}
|
{getNoteStatusText(note.status)}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -815,7 +775,7 @@ const CheckNoteManagement: React.FC<CheckNoteManagementProps> = ({
|
||||||
<option value="all">Tüm Durumlar</option>
|
<option value="all">Tüm Durumlar</option>
|
||||||
{Object.values(CheckStatusEnum).map((status) => (
|
{Object.values(CheckStatusEnum).map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
{getCheckStatusLabel(status)}
|
{getCheckStatusText(status)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -828,7 +788,7 @@ const CheckNoteManagement: React.FC<CheckNoteManagementProps> = ({
|
||||||
<option value="all">Tüm Durumlar</option>
|
<option value="all">Tüm Durumlar</option>
|
||||||
{Object.values(NoteStatusEnum).map((status) => (
|
{Object.values(NoteStatusEnum).map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
{getNoteStatusLabel(status)}
|
{getNoteStatusText(status)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ import DemandForecastFormModal from './DemandForecastFormModal'
|
||||||
import MaterialRequirementFormModal from './MaterialRequirementFormModal'
|
import MaterialRequirementFormModal from './MaterialRequirementFormModal'
|
||||||
import Widget from '../../../components/common/Widget'
|
import Widget from '../../../components/common/Widget'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
|
import {
|
||||||
|
getAccuracyColor,
|
||||||
|
getForecastMethodText,
|
||||||
|
getRequirementSourceTypeColor,
|
||||||
|
getRequirementSourceTypeText,
|
||||||
|
} from '@/utils/erp'
|
||||||
|
|
||||||
const DemandPlanning: React.FC = () => {
|
const DemandPlanning: React.FC = () => {
|
||||||
// Mock data - in a real app, this would come from a store/API
|
// Mock data - in a real app, this would come from a store/API
|
||||||
|
|
@ -236,42 +242,6 @@ const DemandPlanning: React.FC = () => {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
const getMethodLabel = (method: ForecastMethodEnum) => {
|
|
||||||
const methodLabels = {
|
|
||||||
[ForecastMethodEnum.MovingAverage]: 'Hareketli Ortalama',
|
|
||||||
[ForecastMethodEnum.ExponentialSmoothing]: 'Üstel Yumuşatma',
|
|
||||||
[ForecastMethodEnum.LinearRegression]: 'Doğrusal Regresyon',
|
|
||||||
[ForecastMethodEnum.Seasonal]: 'Mevsimsel',
|
|
||||||
}
|
|
||||||
return methodLabels[method]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSourceTypeLabel = (sourceType: RequirementSourceTypeEnum) => {
|
|
||||||
const sourceLabels = {
|
|
||||||
[RequirementSourceTypeEnum.SalesOrder]: 'Satış Siparişi',
|
|
||||||
[RequirementSourceTypeEnum.Forecast]: 'Tahmin',
|
|
||||||
[RequirementSourceTypeEnum.SafetyStock]: 'Güvenlik Stoku',
|
|
||||||
[RequirementSourceTypeEnum.ProductionOrder]: 'Üretim Emri',
|
|
||||||
}
|
|
||||||
return sourceLabels[sourceType]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSourceTypeColor = (sourceType: RequirementSourceTypeEnum) => {
|
|
||||||
const sourceColors = {
|
|
||||||
[RequirementSourceTypeEnum.SalesOrder]: 'bg-blue-100 text-blue-800',
|
|
||||||
[RequirementSourceTypeEnum.Forecast]: 'bg-green-100 text-green-800',
|
|
||||||
[RequirementSourceTypeEnum.SafetyStock]: 'bg-orange-100 text-orange-800',
|
|
||||||
[RequirementSourceTypeEnum.ProductionOrder]: 'bg-purple-100 text-purple-800',
|
|
||||||
}
|
|
||||||
return sourceColors[sourceType]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAccuracyColor = (accuracy: number) => {
|
|
||||||
if (accuracy >= 90) return 'text-green-600'
|
|
||||||
if (accuracy >= 75) return 'text-yellow-600'
|
|
||||||
return 'text-red-600'
|
|
||||||
}
|
|
||||||
|
|
||||||
const forecastColumns: Column<MrpDemandForecast>[] = [
|
const forecastColumns: Column<MrpDemandForecast>[] = [
|
||||||
{
|
{
|
||||||
key: 'material',
|
key: 'material',
|
||||||
|
|
@ -305,7 +275,7 @@ const DemandPlanning: React.FC = () => {
|
||||||
render: (forecast: MrpDemandForecast) => (
|
render: (forecast: MrpDemandForecast) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaCalculator className="w-4 h-4 text-gray-400" />
|
<FaCalculator className="w-4 h-4 text-gray-400" />
|
||||||
<span>{getMethodLabel(forecast.forecastMethod)}</span>
|
<span>{getForecastMethodText(forecast.forecastMethod)}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -397,11 +367,11 @@ const DemandPlanning: React.FC = () => {
|
||||||
header: 'Kaynak',
|
header: 'Kaynak',
|
||||||
render: (req: MrpMaterialRequirement) => (
|
render: (req: MrpMaterialRequirement) => (
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 text-xs font-medium rounded-full ${getSourceTypeColor(
|
className={`px-2 py-1 text-xs font-medium rounded-full ${getRequirementSourceTypeColor(
|
||||||
req.sourceType,
|
req.sourceType,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getSourceTypeLabel(req.sourceType)}
|
{getRequirementSourceTypeText(req.sourceType)}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -519,9 +489,7 @@ const DemandPlanning: React.FC = () => {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Talep Planlama</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Talep Planlama</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Talep tahmini ve malzeme ihtiyaç hesaplama</p>
|
||||||
Talep tahmini ve malzeme ihtiyaç hesaplama
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
|
|
@ -584,7 +552,7 @@ const DemandPlanning: React.FC = () => {
|
||||||
<div className="flex items-center justify-center gap-2 mb-2">
|
<div className="flex items-center justify-center gap-2 mb-2">
|
||||||
<FaCalculator className="w-4 h-4 text-gray-400" />
|
<FaCalculator className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
{getMethodLabel(method)}
|
{getForecastMethodText(method)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 mb-1">{count}</div>
|
<div className="text-2xl font-bold text-gray-900 mb-1">{count}</div>
|
||||||
|
|
@ -603,11 +571,11 @@ const DemandPlanning: React.FC = () => {
|
||||||
{sourceDistribution.map(({ sourceType, count, totalRequirement }) => (
|
{sourceDistribution.map(({ sourceType, count, totalRequirement }) => (
|
||||||
<div key={sourceType} className="text-center p-3 border rounded-lg bg-white">
|
<div key={sourceType} className="text-center p-3 border rounded-lg bg-white">
|
||||||
<div
|
<div
|
||||||
className={`inline-block px-2 py-1 text-xs font-medium rounded-full mb-2 ${getSourceTypeColor(
|
className={`inline-block px-2 py-1 text-xs font-medium rounded-full mb-2 ${getRequirementSourceTypeColor(
|
||||||
sourceType,
|
sourceType,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getSourceTypeLabel(sourceType)}
|
{getRequirementSourceTypeText(sourceType)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 mb-1">{count}</div>
|
<div className="text-2xl font-bold text-gray-900 mb-1">{count}</div>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
|
|
@ -672,7 +640,7 @@ const DemandPlanning: React.FC = () => {
|
||||||
<option value="all">Tüm Yöntemler</option>
|
<option value="all">Tüm Yöntemler</option>
|
||||||
{Object.values(ForecastMethodEnum).map((method) => (
|
{Object.values(ForecastMethodEnum).map((method) => (
|
||||||
<option key={method} value={method}>
|
<option key={method} value={method}>
|
||||||
{getMethodLabel(method)}
|
{getForecastMethodText(method)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,96 @@
|
||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
FaUser,
|
FaUser,
|
||||||
FaChevronDown,
|
FaChevronDown,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaChevronLeft,
|
FaChevronLeft,
|
||||||
FaChevronRight as FaArrowRight,
|
FaChevronRight as FaArrowRight,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { PsGanttTask } from "../../../types/ps";
|
import { PsGanttTask } from '../../../types/ps'
|
||||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||||
import { mockProductionOrders } from "../../../mocks/mockProductionOrders";
|
import { mockProductionOrders } from '../../../mocks/mockProductionOrders'
|
||||||
import { mockWorkOrders } from "../../../mocks/mockWorkOrders";
|
import { mockWorkOrders } from '../../../mocks/mockWorkOrders'
|
||||||
import { mockWorkCenters } from "../../../mocks/mockWorkCenters";
|
import { mockWorkCenters } from '../../../mocks/mockWorkCenters'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
import {
|
import { getPriorityColor, getProductionOrderStatus, getWorkOrderStatus } from '../../../utils/erp'
|
||||||
getProductionOrderStatus,
|
import { Container } from '@/components/shared'
|
||||||
getWorkOrderStatus,
|
|
||||||
} from "../../../utils/erp";
|
|
||||||
import { Container } from "@/components/shared";
|
|
||||||
|
|
||||||
interface PlanningGanttProps {
|
interface PlanningGanttProps {
|
||||||
workCenterId?: string;
|
workCenterId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
const PlanningGantt: React.FC<PlanningGanttProps> = ({ workCenterId }) => {
|
||||||
workCenterId,
|
|
||||||
}) => {
|
|
||||||
const getInitialExpandedItems = () => {
|
const getInitialExpandedItems = () => {
|
||||||
const expandedItems = new Set<string>();
|
const expandedItems = new Set<string>()
|
||||||
const firstTwoOrders = mockProductionOrders.slice(0, 2);
|
const firstTwoOrders = mockProductionOrders.slice(0, 2)
|
||||||
firstTwoOrders.forEach((order) => {
|
firstTwoOrders.forEach((order) => {
|
||||||
expandedItems.add(`prod-${order.id}`);
|
expandedItems.add(`prod-${order.id}`)
|
||||||
const orderWorkOrders = mockWorkOrders.filter(
|
const orderWorkOrders = mockWorkOrders.filter((wo) => wo.productionOrderId === order.id)
|
||||||
(wo) => wo.productionOrderId === order.id
|
|
||||||
);
|
|
||||||
orderWorkOrders.forEach((wo) => {
|
orderWorkOrders.forEach((wo) => {
|
||||||
expandedItems.add(`work-${wo.id}`);
|
expandedItems.add(`work-${wo.id}`)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
return expandedItems;
|
return expandedItems
|
||||||
};
|
}
|
||||||
|
|
||||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(getInitialExpandedItems())
|
||||||
getInitialExpandedItems()
|
const [selectedWorkCenter, setSelectedWorkCenter] = useState<string>(workCenterId || '')
|
||||||
);
|
const [viewMode, setViewMode] = useState<'day' | 'week' | 'month' | 'year'>('week')
|
||||||
const [selectedWorkCenter, setSelectedWorkCenter] = useState<string>(
|
const [currentDate, setCurrentDate] = useState<Date>(new Date())
|
||||||
workCenterId || ""
|
|
||||||
);
|
|
||||||
const [viewMode, setViewMode] = useState<"day" | "week" | "month" | "year">(
|
|
||||||
"week"
|
|
||||||
);
|
|
||||||
const [currentDate, setCurrentDate] = useState<Date>(new Date());
|
|
||||||
|
|
||||||
const generateDateRange = () => {
|
const generateDateRange = () => {
|
||||||
const startDate = new Date(currentDate);
|
const startDate = new Date(currentDate)
|
||||||
|
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
const hours = [];
|
const hours = []
|
||||||
for (let i = 0; i < 24; i++) {
|
for (let i = 0; i < 24; i++) {
|
||||||
const hour = new Date(startDate);
|
const hour = new Date(startDate)
|
||||||
hour.setHours(i, 0, 0, 0);
|
hour.setHours(i, 0, 0, 0)
|
||||||
hours.push(hour);
|
hours.push(hour)
|
||||||
}
|
}
|
||||||
return hours;
|
return hours
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
const weekStart = new Date(startDate);
|
const weekStart = new Date(startDate)
|
||||||
const dayOfWeek = weekStart.getDay();
|
const dayOfWeek = weekStart.getDay()
|
||||||
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1
|
||||||
weekStart.setDate(weekStart.getDate() - daysToSubtract);
|
weekStart.setDate(weekStart.getDate() - daysToSubtract)
|
||||||
weekStart.setHours(0, 0, 0, 0);
|
weekStart.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
const dates = [];
|
const dates = []
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
const date = new Date(weekStart);
|
const date = new Date(weekStart)
|
||||||
date.setDate(weekStart.getDate() + i);
|
date.setDate(weekStart.getDate() + i)
|
||||||
dates.push(date);
|
dates.push(date)
|
||||||
}
|
}
|
||||||
return dates;
|
return dates
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
const monthStart = new Date(
|
const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
|
||||||
currentDate.getFullYear(),
|
|
||||||
currentDate.getMonth(),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
const daysInMonth = new Date(
|
const daysInMonth = new Date(
|
||||||
currentDate.getFullYear(),
|
currentDate.getFullYear(),
|
||||||
currentDate.getMonth() + 1,
|
currentDate.getMonth() + 1,
|
||||||
0
|
0,
|
||||||
).getDate();
|
).getDate()
|
||||||
|
|
||||||
const dates = [];
|
const dates = []
|
||||||
for (let i = 0; i < daysInMonth; i++) {
|
for (let i = 0; i < daysInMonth; i++) {
|
||||||
const date = new Date(monthStart);
|
const date = new Date(monthStart)
|
||||||
date.setDate(monthStart.getDate() + i);
|
date.setDate(monthStart.getDate() + i)
|
||||||
dates.push(date);
|
dates.push(date)
|
||||||
}
|
}
|
||||||
return dates;
|
return dates
|
||||||
} else {
|
} else {
|
||||||
const yearStart = new Date(currentDate.getFullYear(), 0, 1);
|
const yearStart = new Date(currentDate.getFullYear(), 0, 1)
|
||||||
const months = [];
|
const months = []
|
||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
const month = new Date(yearStart);
|
const month = new Date(yearStart)
|
||||||
month.setMonth(i);
|
month.setMonth(i)
|
||||||
months.push(month);
|
months.push(month)
|
||||||
|
}
|
||||||
|
return months
|
||||||
}
|
}
|
||||||
return months;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const dateRange = generateDateRange();
|
const dateRange = generateDateRange()
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
const createGanttData = (): PsGanttTask[] => {
|
const createGanttData = (): PsGanttTask[] => {
|
||||||
|
|
@ -117,12 +100,10 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
.map((wo) => ({
|
.map((wo) => ({
|
||||||
id: `work-${wo.id}`,
|
id: `work-${wo.id}`,
|
||||||
name: wo.operation?.name || wo.workOrderNumber,
|
name: wo.operation?.name || wo.workOrderNumber,
|
||||||
type: "task" as const,
|
type: 'task' as const,
|
||||||
startDate: wo.plannedStartDate,
|
startDate: wo.plannedStartDate,
|
||||||
endDate: wo.plannedEndDate,
|
endDate: wo.plannedEndDate,
|
||||||
progress:
|
progress: Math.round((wo.confirmedQuantity / wo.plannedQuantity) * 100) || 0,
|
||||||
Math.round((wo.confirmedQuantity / wo.plannedQuantity) * 100) ||
|
|
||||||
0,
|
|
||||||
assignee: wo.assignedOperators[0]
|
assignee: wo.assignedOperators[0]
|
||||||
? mockEmployees.find((e) => e.id === wo.assignedOperators[0])
|
? mockEmployees.find((e) => e.id === wo.assignedOperators[0])
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
@ -132,239 +113,200 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
children: [],
|
children: [],
|
||||||
estimatedHours: wo.processTime / 60,
|
estimatedHours: wo.processTime / 60,
|
||||||
hoursWorked: wo.actualProcessTime ? wo.actualProcessTime / 60 : 0,
|
hoursWorked: wo.actualProcessTime ? wo.actualProcessTime / 60 : 0,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `prod-${order.id}`,
|
id: `prod-${order.id}`,
|
||||||
name: `${order.orderNumber} - ${order.materials
|
name: `${order.orderNumber} - ${order.materials.map((m) => m.material?.name).join(', ')}`,
|
||||||
.map((m) => m.material?.name)
|
type: 'project' as const,
|
||||||
.join(", ")}`,
|
|
||||||
type: "project" as const,
|
|
||||||
startDate: order.plannedStartDate,
|
startDate: order.plannedStartDate,
|
||||||
endDate: order.plannedEndDate,
|
endDate: order.plannedEndDate,
|
||||||
progress:
|
progress:
|
||||||
order.confirmedQuantity && order.plannedQuantity
|
order.confirmedQuantity && order.plannedQuantity
|
||||||
? Math.round(
|
? Math.round((order.confirmedQuantity / order.plannedQuantity) * 100)
|
||||||
(order.confirmedQuantity / order.plannedQuantity) * 100
|
|
||||||
)
|
|
||||||
: 0,
|
: 0,
|
||||||
status: getProductionOrderStatus(order.status),
|
status: getProductionOrderStatus(order.status),
|
||||||
priority: order.priority,
|
priority: order.priority,
|
||||||
level: 0,
|
level: 0,
|
||||||
children: workOrders,
|
children: workOrders,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const filterByWorkCenter = (tasks: PsGanttTask[]): PsGanttTask[] => {
|
const filterByWorkCenter = (tasks: PsGanttTask[]): PsGanttTask[] => {
|
||||||
if (!selectedWorkCenter) return tasks;
|
if (!selectedWorkCenter) return tasks
|
||||||
return tasks
|
return tasks
|
||||||
.map((task) => {
|
.map((task) => {
|
||||||
if (
|
if (task.type === 'task' && task.assignee?.id !== selectedWorkCenter) {
|
||||||
task.type === "task" &&
|
return null
|
||||||
task.assignee?.id !== selectedWorkCenter
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.children) {
|
if (task.children) {
|
||||||
const filteredChildren = filterByWorkCenter(task.children).filter(
|
const filteredChildren = filterByWorkCenter(task.children).filter(
|
||||||
Boolean
|
Boolean,
|
||||||
) as PsGanttTask[];
|
) as PsGanttTask[]
|
||||||
if (filteredChildren.length > 0) {
|
if (filteredChildren.length > 0) {
|
||||||
return { ...task, children: filteredChildren };
|
return { ...task, children: filteredChildren }
|
||||||
} else if (
|
} else if (task.type === 'task' && task.assignee?.id === selectedWorkCenter) {
|
||||||
task.type === "task" &&
|
return task
|
||||||
task.assignee?.id === selectedWorkCenter
|
|
||||||
) {
|
|
||||||
return task;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return task;
|
return task
|
||||||
})
|
})
|
||||||
.filter(Boolean) as PsGanttTask[];
|
.filter(Boolean) as PsGanttTask[]
|
||||||
};
|
}
|
||||||
|
|
||||||
const allGanttTasks = createGanttData();
|
const allGanttTasks = createGanttData()
|
||||||
return filterByWorkCenter(allGanttTasks);
|
return filterByWorkCenter(allGanttTasks)
|
||||||
}, [selectedWorkCenter]);
|
}, [selectedWorkCenter])
|
||||||
|
|
||||||
const toggleExpand = (id: string) => {
|
const toggleExpand = (id: string) => {
|
||||||
const newExpanded = new Set(expandedItems);
|
const newExpanded = new Set(expandedItems)
|
||||||
if (newExpanded.has(id)) {
|
if (newExpanded.has(id)) {
|
||||||
newExpanded.delete(id);
|
newExpanded.delete(id)
|
||||||
} else {
|
} else {
|
||||||
newExpanded.add(id);
|
newExpanded.add(id)
|
||||||
}
|
}
|
||||||
setExpandedItems(newExpanded);
|
setExpandedItems(newExpanded)
|
||||||
};
|
|
||||||
|
|
||||||
const getPriorityColor = (priority: PriorityEnum) => {
|
|
||||||
switch (priority) {
|
|
||||||
case PriorityEnum.High:
|
|
||||||
return "text-red-600 bg-red-50 border-red-200";
|
|
||||||
case PriorityEnum.Normal:
|
|
||||||
return "text-blue-600 bg-blue-50 border-blue-200";
|
|
||||||
case PriorityEnum.Low:
|
|
||||||
return "text-green-600 bg-green-50 border-green-200";
|
|
||||||
default:
|
|
||||||
return "text-gray-600 bg-gray-50 border-gray-200";
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const navigatePrevious = () => {
|
const navigatePrevious = () => {
|
||||||
const newDate = new Date(currentDate);
|
const newDate = new Date(currentDate)
|
||||||
if (viewMode === "day") newDate.setDate(newDate.getDate() - 1);
|
if (viewMode === 'day') newDate.setDate(newDate.getDate() - 1)
|
||||||
else if (viewMode === "week") newDate.setDate(newDate.getDate() - 7);
|
else if (viewMode === 'week') newDate.setDate(newDate.getDate() - 7)
|
||||||
else if (viewMode === "month") newDate.setMonth(newDate.getMonth() - 1);
|
else if (viewMode === 'month') newDate.setMonth(newDate.getMonth() - 1)
|
||||||
else newDate.setFullYear(newDate.getFullYear() - 1);
|
else newDate.setFullYear(newDate.getFullYear() - 1)
|
||||||
setCurrentDate(newDate);
|
setCurrentDate(newDate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const navigateNext = () => {
|
const navigateNext = () => {
|
||||||
const newDate = new Date(currentDate);
|
const newDate = new Date(currentDate)
|
||||||
if (viewMode === "day") newDate.setDate(newDate.getDate() + 1);
|
if (viewMode === 'day') newDate.setDate(newDate.getDate() + 1)
|
||||||
else if (viewMode === "week") newDate.setDate(newDate.getDate() + 7);
|
else if (viewMode === 'week') newDate.setDate(newDate.getDate() + 7)
|
||||||
else if (viewMode === "month") newDate.setMonth(newDate.getMonth() + 1);
|
else if (viewMode === 'month') newDate.setMonth(newDate.getMonth() + 1)
|
||||||
else newDate.setFullYear(newDate.getFullYear() + 1);
|
else newDate.setFullYear(newDate.getFullYear() + 1)
|
||||||
setCurrentDate(newDate);
|
setCurrentDate(newDate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const navigateToday = () => {
|
const navigateToday = () => {
|
||||||
setCurrentDate(new Date());
|
setCurrentDate(new Date())
|
||||||
};
|
}
|
||||||
|
|
||||||
const getCurrentDateInfo = () => {
|
const getCurrentDateInfo = () => {
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
return currentDate.toLocaleDateString("tr-TR", {
|
return currentDate.toLocaleDateString('tr-TR', {
|
||||||
weekday: "long",
|
weekday: 'long',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
month: "long",
|
month: 'long',
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
});
|
})
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
const weekStart = new Date(currentDate);
|
const weekStart = new Date(currentDate)
|
||||||
const dayOfWeek = weekStart.getDay();
|
const dayOfWeek = weekStart.getDay()
|
||||||
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1
|
||||||
weekStart.setDate(weekStart.getDate() - daysToSubtract);
|
weekStart.setDate(weekStart.getDate() - daysToSubtract)
|
||||||
const weekEnd = new Date(weekStart);
|
const weekEnd = new Date(weekStart)
|
||||||
weekEnd.setDate(weekStart.getDate() + 6);
|
weekEnd.setDate(weekStart.getDate() + 6)
|
||||||
return `${weekStart.toLocaleDateString("tr-TR", {
|
return `${weekStart.toLocaleDateString('tr-TR', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short',
|
||||||
})} - ${weekEnd.toLocaleDateString("tr-TR", {
|
})} - ${weekEnd.toLocaleDateString('tr-TR', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
})}`;
|
})}`
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
return currentDate.toLocaleDateString("tr-TR", {
|
return currentDate.toLocaleDateString('tr-TR', {
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
month: "long",
|
month: 'long',
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return currentDate.toLocaleDateString("tr-TR", { year: "numeric" });
|
return currentDate.toLocaleDateString('tr-TR', { year: 'numeric' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const calculateTaskPosition = (startDate: Date, endDate: Date) => {
|
const calculateTaskPosition = (startDate: Date, endDate: Date) => {
|
||||||
const chartStart = dateRange[0];
|
const chartStart = dateRange[0]
|
||||||
const chartEnd = dateRange[dateRange.length - 1];
|
const chartEnd = dateRange[dateRange.length - 1]
|
||||||
const taskStart = new Date(startDate);
|
const taskStart = new Date(startDate)
|
||||||
const taskEnd = new Date(endDate);
|
const taskEnd = new Date(endDate)
|
||||||
|
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
const dayStart = new Date(chartStart);
|
const dayStart = new Date(chartStart)
|
||||||
dayStart.setHours(0, 0, 0, 0);
|
dayStart.setHours(0, 0, 0, 0)
|
||||||
const dayEnd = new Date(chartStart);
|
const dayEnd = new Date(chartStart)
|
||||||
dayEnd.setHours(23, 59, 59, 999);
|
dayEnd.setHours(23, 59, 59, 999)
|
||||||
if (taskEnd < dayStart || taskStart > dayEnd)
|
if (taskEnd < dayStart || taskStart > dayEnd)
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
const startHour =
|
const startHour =
|
||||||
taskStart > dayStart
|
taskStart > dayStart ? taskStart.getHours() + taskStart.getMinutes() / 60 : 0
|
||||||
? taskStart.getHours() + taskStart.getMinutes() / 60
|
const endHour = taskEnd < dayEnd ? taskEnd.getHours() + taskEnd.getMinutes() / 60 : 24
|
||||||
: 0;
|
const left = (startHour / 24) * 100
|
||||||
const endHour =
|
const width = Math.max(1, ((endHour - startHour) / 24) * 100)
|
||||||
taskEnd < dayEnd ? taskEnd.getHours() + taskEnd.getMinutes() / 60 : 24;
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
const left = (startHour / 24) * 100;
|
} else if (viewMode === 'week') {
|
||||||
const width = Math.max(1, ((endHour - startHour) / 24) * 100);
|
const weekStart = new Date(chartStart)
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
const weekEnd = new Date(chartEnd)
|
||||||
} else if (viewMode === "week") {
|
weekEnd.setHours(23, 59, 59, 999)
|
||||||
const weekStart = new Date(chartStart);
|
|
||||||
const weekEnd = new Date(chartEnd);
|
|
||||||
weekEnd.setHours(23, 59, 59, 999);
|
|
||||||
if (taskEnd < weekStart || taskStart > weekEnd)
|
if (taskEnd < weekStart || taskStart > weekEnd)
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
const startDay =
|
const startDay =
|
||||||
taskStart > weekStart
|
taskStart > weekStart
|
||||||
? Math.floor(
|
? Math.floor((taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskStart.getTime() - weekStart.getTime()) /
|
: 0
|
||||||
(1000 * 60 * 60 * 24)
|
|
||||||
)
|
|
||||||
: 0;
|
|
||||||
const endDay =
|
const endDay =
|
||||||
taskEnd < weekEnd
|
taskEnd < weekEnd
|
||||||
? Math.ceil(
|
? Math.ceil((taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
|
: 7
|
||||||
)
|
const left = (startDay / 7) * 100
|
||||||
: 7;
|
const width = Math.max(1, ((endDay - startDay) / 7) * 100)
|
||||||
const left = (startDay / 7) * 100;
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
const width = Math.max(1, ((endDay - startDay) / 7) * 100);
|
} else if (viewMode === 'month') {
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
const monthStart = new Date(chartStart)
|
||||||
} else if (viewMode === "month") {
|
const monthEnd = new Date(chartEnd)
|
||||||
const monthStart = new Date(chartStart);
|
monthEnd.setHours(23, 59, 59, 999)
|
||||||
const monthEnd = new Date(chartEnd);
|
|
||||||
monthEnd.setHours(23, 59, 59, 999);
|
|
||||||
if (taskEnd < monthStart || taskStart > monthEnd)
|
if (taskEnd < monthStart || taskStart > monthEnd)
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
const daysInMonth = dateRange.length;
|
const daysInMonth = dateRange.length
|
||||||
const startDay =
|
const startDay =
|
||||||
taskStart > monthStart
|
taskStart > monthStart
|
||||||
? Math.floor(
|
? Math.floor((taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskStart.getTime() - monthStart.getTime()) /
|
: 0
|
||||||
(1000 * 60 * 60 * 24)
|
|
||||||
)
|
|
||||||
: 0;
|
|
||||||
const endDay =
|
const endDay =
|
||||||
taskEnd < monthEnd
|
taskEnd < monthEnd
|
||||||
? Math.ceil(
|
? Math.ceil((taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
|
: daysInMonth
|
||||||
)
|
const left = (startDay / daysInMonth) * 100
|
||||||
: daysInMonth;
|
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100)
|
||||||
const left = (startDay / daysInMonth) * 100;
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100);
|
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
|
||||||
} else {
|
} else {
|
||||||
const yearStart = new Date(chartStart);
|
const yearStart = new Date(chartStart)
|
||||||
const yearEnd = new Date(chartEnd);
|
const yearEnd = new Date(chartEnd)
|
||||||
yearEnd.setMonth(11, 31);
|
yearEnd.setMonth(11, 31)
|
||||||
yearEnd.setHours(23, 59, 59, 999);
|
yearEnd.setHours(23, 59, 59, 999)
|
||||||
if (taskEnd < yearStart || taskStart > yearEnd)
|
if (taskEnd < yearStart || taskStart > yearEnd)
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
const startMonth = taskStart > yearStart ? taskStart.getMonth() : 0;
|
const startMonth = taskStart > yearStart ? taskStart.getMonth() : 0
|
||||||
const endMonth = taskEnd < yearEnd ? taskEnd.getMonth() + 1 : 12;
|
const endMonth = taskEnd < yearEnd ? taskEnd.getMonth() + 1 : 12
|
||||||
const left = (startMonth / 12) * 100;
|
const left = (startMonth / 12) * 100
|
||||||
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100);
|
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100)
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const renderTask = (task: PsGanttTask): React.ReactNode => {
|
const renderTask = (task: PsGanttTask): React.ReactNode => {
|
||||||
const hasChildren = task.children && task.children.length > 0;
|
const hasChildren = task.children && task.children.length > 0
|
||||||
const isExpanded = expandedItems.has(task.id);
|
const isExpanded = expandedItems.has(task.id)
|
||||||
const indent = task.level * 20;
|
const indent = task.level * 20
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={task.id}>
|
<React.Fragment key={task.id}>
|
||||||
<div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[36px]">
|
<div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[36px]">
|
||||||
{/* Task Info */}
|
{/* Task Info */}
|
||||||
<div className="w-1/3 sm:w-1/4 p-1 border-r border-gray-200">
|
<div className="w-1/3 sm:w-1/4 p-1 border-r border-gray-200">
|
||||||
<div
|
<div className="flex items-center" style={{ paddingLeft: `${Math.min(indent, 30)}px` }}>
|
||||||
className="flex items-center"
|
|
||||||
style={{ paddingLeft: `${Math.min(indent, 30)}px` }}
|
|
||||||
>
|
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleExpand(task.id)}
|
onClick={() => toggleExpand(task.id)}
|
||||||
|
|
@ -383,9 +325,7 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
<div className="flex items-center gap-1 sm:gap-2">
|
<div className="flex items-center gap-1 sm:gap-2">
|
||||||
<span
|
<span
|
||||||
className={`font-medium text-xs truncate ${
|
className={`font-medium text-xs truncate ${
|
||||||
task.type === "project"
|
task.type === 'project' ? 'text-blue-900 font-semibold' : 'text-gray-900'
|
||||||
? "text-blue-900 font-semibold"
|
|
||||||
: "text-gray-900"
|
|
||||||
}`}
|
}`}
|
||||||
title={task.name}
|
title={task.name}
|
||||||
>
|
>
|
||||||
|
|
@ -393,14 +333,14 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`px-1 py-0 text-[10px] border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
|
className={`px-1 py-0 text-[10px] border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
|
||||||
task.priority
|
task.priority,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{task.priority}
|
{task.priority}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.type === "task" && task.assignee && (
|
{task.type === 'task' && task.assignee && (
|
||||||
<div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600">
|
<div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600">
|
||||||
<FaUser className="h-2 w-2 flex-shrink-0" />
|
<FaUser className="h-2 w-2 flex-shrink-0" />
|
||||||
<span className="truncate text-xs">
|
<span className="truncate text-xs">
|
||||||
|
|
@ -415,8 +355,8 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-1 text-xs text-gray-500 truncate hidden sm:block">
|
<div className="mt-1 text-xs text-gray-500 truncate hidden sm:block">
|
||||||
{task.startDate.toLocaleDateString("tr-TR")} -{" "}
|
{task.startDate.toLocaleDateString('tr-TR')} -{' '}
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
{task.endDate.toLocaleDateString('tr-TR')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -426,12 +366,9 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
<div className="w-2/3 sm:w-3/4 p-1 relative">
|
<div className="w-2/3 sm:w-3/4 p-1 relative">
|
||||||
<div className="relative h-6 bg-gray-100 rounded">
|
<div className="relative h-6 bg-gray-100 rounded">
|
||||||
{(() => {
|
{(() => {
|
||||||
const position = calculateTaskPosition(
|
const position = calculateTaskPosition(task.startDate, task.endDate)
|
||||||
task.startDate,
|
|
||||||
task.endDate
|
|
||||||
);
|
|
||||||
|
|
||||||
if (task.type === "task" && position.isVisible) {
|
if (task.type === 'task' && position.isVisible) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute h-5 mt-0.5 rounded shadow-sm group cursor-pointer border border-white border-opacity-20`}
|
className={`absolute h-5 mt-0.5 rounded shadow-sm group cursor-pointer border border-white border-opacity-20`}
|
||||||
|
|
@ -449,24 +386,16 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
||||||
<div className="font-semibold">{task.name}</div>
|
<div className="font-semibold">{task.name}</div>
|
||||||
<div className="mt-1">
|
<div className="mt-1">İlerleme: {task.progress}% tamamlandı</div>
|
||||||
İlerleme: {task.progress}% tamamlandı
|
<div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
|
||||||
</div>
|
<div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
|
||||||
<div>
|
|
||||||
Planlanan Başlangıç:{" "}
|
|
||||||
{task.startDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Planlanan Bitiş:{" "}
|
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.type !== "task" && position.isVisible) {
|
if (task.type !== 'task' && position.isVisible) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="absolute h-2 mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30"
|
className="absolute h-2 mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30"
|
||||||
|
|
@ -475,44 +404,32 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
>
|
>
|
||||||
<div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
<div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
||||||
<div className="font-semibold">{task.name}</div>
|
<div className="font-semibold">{task.name}</div>
|
||||||
<div>
|
<div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
|
||||||
Planlanan Başlangıç:{" "}
|
<div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
|
||||||
{task.startDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Planlanan Bitiş:{" "}
|
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasChildren &&
|
{hasChildren && isExpanded && task.children?.map((child) => renderTask(child))}
|
||||||
isExpanded &&
|
|
||||||
task.children?.map((child) => renderTask(child))}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">
|
<h2 className="text-2xl font-bold text-gray-900">Planlama Gantt Şeması</h2>
|
||||||
Planlama Gantt Şeması
|
<p className="text-gray-600">Üretim ve iş emirlerinizi zaman çizelgesinde yönetin.</p>
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600">
|
|
||||||
Üretim ve iş emirlerinizi zaman çizelgesinde yönetin.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -560,11 +477,7 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={viewMode}
|
value={viewMode}
|
||||||
onChange={(e) =>
|
onChange={(e) => setViewMode(e.target.value as 'day' | 'week' | 'month' | 'year')}
|
||||||
setViewMode(
|
|
||||||
e.target.value as "day" | "week" | "month" | "year"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
|
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<option value="day">Günlük</option>
|
<option value="day">Günlük</option>
|
||||||
|
|
@ -583,28 +496,25 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
<div className="w-2/3 sm:w-3/4 p-2 bg-gray-50">
|
<div className="w-2/3 sm:w-3/4 p-2 bg-gray-50">
|
||||||
<div className="flex justify-between text-xs font-semibold">
|
<div className="flex justify-between text-xs font-semibold">
|
||||||
{dateRange.map((date) => (
|
{dateRange.map((date) => (
|
||||||
<div
|
<div key={date.toISOString()} className="text-center min-w-0 flex-1">
|
||||||
key={date.toISOString()}
|
|
||||||
className="text-center min-w-0 flex-1"
|
|
||||||
>
|
|
||||||
<div className="truncate">
|
<div className="truncate">
|
||||||
{viewMode === "day"
|
{viewMode === 'day'
|
||||||
? date.toLocaleTimeString("tr-TR", {
|
? date.toLocaleTimeString('tr-TR', {
|
||||||
hour: "2-digit",
|
hour: '2-digit',
|
||||||
minute: "2-digit",
|
minute: '2-digit',
|
||||||
})
|
})
|
||||||
: viewMode === "week"
|
: viewMode === 'week'
|
||||||
? date.toLocaleDateString("tr-TR", { day: "2-digit" })
|
? date.toLocaleDateString('tr-TR', { day: '2-digit' })
|
||||||
: viewMode === "year"
|
: viewMode === 'year'
|
||||||
? date.toLocaleDateString("tr-TR", { month: "short" })
|
? date.toLocaleDateString('tr-TR', { month: 'short' })
|
||||||
: date.toLocaleDateString("tr-TR", { day: "2-digit" })}
|
: date.toLocaleDateString('tr-TR', { day: '2-digit' })}
|
||||||
</div>
|
</div>
|
||||||
{viewMode !== "day" && viewMode !== "year" && (
|
{viewMode !== 'day' && viewMode !== 'year' && (
|
||||||
<div className="text-xs text-gray-500 hidden sm:block">
|
<div className="text-xs text-gray-500 hidden sm:block">
|
||||||
{date.toLocaleDateString("tr-TR", { weekday: "short" })}
|
{date.toLocaleDateString('tr-TR', { weekday: 'short' })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{viewMode === "year" && (
|
{viewMode === 'year' && (
|
||||||
<div className="text-xs text-gray-500 hidden sm:block">
|
<div className="text-xs text-gray-500 hidden sm:block">
|
||||||
{date.getFullYear()}
|
{date.getFullYear()}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -615,12 +525,10 @@ const PlanningGantt: React.FC<PlanningGanttProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">{filteredData.map((task) => renderTask(task))}</div>
|
||||||
{filteredData.map((task) => renderTask(task))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default PlanningGantt;
|
export default PlanningGantt
|
||||||
|
|
|
||||||
110
ui/src/views/supplychain/components/MaterialTypeModal.tsx
Normal file
110
ui/src/views/supplychain/components/MaterialTypeModal.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { MaterialTypeEnum, MmMaterialType } from '@/types/mm'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
interface MaterialTypeModalProps {
|
||||||
|
type?: MmMaterialType | null
|
||||||
|
onSave: (data: Partial<MmMaterialType>) => void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({ type, onSave, onClose }) => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
code: type?.code || MaterialTypeEnum.RawMaterial,
|
||||||
|
name: type?.name || '',
|
||||||
|
description: type?.description || '',
|
||||||
|
isActive: type?.isActive ?? true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onSave(formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
|
<div className="bg-white rounded-lg w-full max-w-xs">
|
||||||
|
<div className="px-3 py-2 border-b border-gray-200">
|
||||||
|
<h3 className="text-sm font-medium text-gray-900">
|
||||||
|
{type ? 'Malzeme Türünü Düzenle' : 'Yeni Malzeme Türü'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">Tür Kodu</label>
|
||||||
|
<select
|
||||||
|
value={formData.code}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
code: e.target.value as MaterialTypeEnum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value={MaterialTypeEnum.RawMaterial}>Hammadde</option>
|
||||||
|
<option value={MaterialTypeEnum.SemiFinished}>Yarı Mamul</option>
|
||||||
|
<option value={MaterialTypeEnum.Finished}>Mamul</option>
|
||||||
|
<option value={MaterialTypeEnum.Consumable}>Sarf Malzemesi</option>
|
||||||
|
<option value={MaterialTypeEnum.Service}>Hizmet</option>
|
||||||
|
<option value={MaterialTypeEnum.Spare}>Yedek Parça</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">Ad</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-700 mb-1">Açıklama</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="isActive"
|
||||||
|
checked={formData.isActive}
|
||||||
|
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
|
||||||
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
||||||
|
Aktif
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-2 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-2.5 py-1 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
|
||||||
|
>
|
||||||
|
İptal
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="px-2.5 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Kaydet
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MaterialTypeModal
|
||||||
|
|
@ -4,6 +4,7 @@ import { MmMaterialType, MaterialTypeEnum } from '../../../types/mm'
|
||||||
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
|
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
|
||||||
import { getMaterialTypeDisplay } from '../../../utils/erp'
|
import { getMaterialTypeDisplay } from '../../../utils/erp'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
|
import MaterialTypeModal from './MaterialTypeModal'
|
||||||
|
|
||||||
const MaterialTypes: React.FC = () => {
|
const MaterialTypes: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
|
@ -265,110 +266,4 @@ const MaterialTypes: React.FC = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MaterialTypeModalProps {
|
|
||||||
type?: MmMaterialType | null
|
|
||||||
onSave: (data: Partial<MmMaterialType>) => void
|
|
||||||
onClose: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({ type, onSave, onClose }) => {
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
code: type?.code || MaterialTypeEnum.RawMaterial,
|
|
||||||
name: type?.name || '',
|
|
||||||
description: type?.description || '',
|
|
||||||
isActive: type?.isActive ?? true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
onSave(formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
|
||||||
<div className="bg-white rounded-lg w-full max-w-xs">
|
|
||||||
<div className="px-3 py-2 border-b border-gray-200">
|
|
||||||
<h3 className="text-sm font-medium text-gray-900">
|
|
||||||
{type ? 'Malzeme Türünü Düzenle' : 'Yeni Malzeme Türü'}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">Tür Kodu</label>
|
|
||||||
<select
|
|
||||||
value={formData.code}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
code: e.target.value as MaterialTypeEnum,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value={MaterialTypeEnum.RawMaterial}>Hammadde</option>
|
|
||||||
<option value={MaterialTypeEnum.SemiFinished}>Yarı Mamul</option>
|
|
||||||
<option value={MaterialTypeEnum.Finished}>Mamul</option>
|
|
||||||
<option value={MaterialTypeEnum.Consumable}>Sarf Malzemesi</option>
|
|
||||||
<option value={MaterialTypeEnum.Service}>Hizmet</option>
|
|
||||||
<option value={MaterialTypeEnum.Spare}>Yedek Parça</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">Ad</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">Açıklama</label>
|
|
||||||
<textarea
|
|
||||||
value={formData.description}
|
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="isActive"
|
|
||||||
checked={formData.isActive}
|
|
||||||
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
|
|
||||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
||||||
/>
|
|
||||||
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
|
||||||
Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 pt-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
className="px-2.5 py-1 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
|
|
||||||
>
|
|
||||||
İptal
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="px-2.5 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
Kaydet
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MaterialTypes
|
export default MaterialTypes
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue